diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 230bf25f33ff3e7070dfed5bbf7363ac8fc5489b..704338c9a69de70461a80f80655ed27201bc0518 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,14 +2,21 @@ This document contains the ``fslpy`` release history in reverse chronological
 order.
 
 
-2.3.0 (Under development)
--------------------------
+2.3.0 (Tuesday June 25th 2019)
+------------------------------
 
 
 Added
 ^^^^^
 
 
+* New :class:`.Bitmap` class, for loading bitmap images. The
+  :meth:`.Bitmap.asImage` method can be used to convert a ``Bitmap`` into
+  an :class:`.Image`.
+* The :class:`.Image` class now has support for the ``RGB24`` and ``RGBA32``
+  NIfTI data types.
+* New :attr:`.Image.nvals` property, for use with ``RGB24``/``RGBA32``
+  images.
 * New :meth:`.LabelAtlas.get` and :meth:`ProbabilisticAtlas.get` methods,
   which return an :class:`.Image` for a specific region.
 * The :meth:`.AtlasDescription.find` method also now a ``name`` parameter,
@@ -24,6 +31,7 @@ Fixed
 
 * The :func:`.makeWriteable` function will always create a copy of an
   ``array`` if its base is a ``bytes`` object.
+* Fixed a bug in the :meth:`.GitfitMesh.loadVertices` method.
 
 
 2.2.0 (Wednesday May 8th 2019)
diff --git a/README.rst b/README.rst
index f4e6d3c31cc50228c8b81c0b09831fef81b1c73c..fb803e03f98b36837d7a739513a37cd432f98476 100644
--- a/README.rst
+++ b/README.rst
@@ -57,6 +57,9 @@ Some extra dependencies are listed in `requirements.txt
   class has some methods which use ``trimesh`` to perform geometric queries
   on the mesh.
 
+- ``Pillow``: The `fsl.data.bitmap.Bitmap <fsl/data/bitmap.py`_ class uses
+  ``Pillow`` to load image files.
+
 
 If you are using Linux, you need to install wxPython first, as binaries are
 not available on PyPI. Install wxPython like so, changing the URL for your
diff --git a/doc/fsl.data.bitmap.rst b/doc/fsl.data.bitmap.rst
new file mode 100644
index 0000000000000000000000000000000000000000..bf736530fde52bff10d228cd8076953b4956e402
--- /dev/null
+++ b/doc/fsl.data.bitmap.rst
@@ -0,0 +1,7 @@
+``fsl.data.bitmap``
+===================
+
+.. automodule:: fsl.data.bitmap
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/doc/fsl.data.rst b/doc/fsl.data.rst
index 576f8f4e46fe24a5bd5a79adeae440388d08d720..ec1e73eda4a17296f19b067444e62d9630f7a17b 100644
--- a/doc/fsl.data.rst
+++ b/doc/fsl.data.rst
@@ -5,6 +5,7 @@
    :hidden:
 
    fsl.data.atlases
+   fsl.data.bitmap
    fsl.data.constants
    fsl.data.dicom
    fsl.data.dtifit
diff --git a/fsl/data/bitmap.py b/fsl/data/bitmap.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec7b6b1fe1c9e12c5fa8e52c6e52589f1be3281c
--- /dev/null
+++ b/fsl/data/bitmap.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+#
+# bitmap.py - The Bitmap class
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module contains the :class:`Bitmap` class, for loading bitmap image
+files. Pillow is required to use the ``Bitmap`` class.
+"""
+
+
+import os.path      as op
+import                 logging
+import                 six
+
+import numpy        as np
+
+from . import image as fslimage
+
+
+log = logging.getLogger(__name__)
+
+
+BITMAP_EXTENSIONS = ['.bmp', '.png',  '.jpg', '.jpeg',
+                     '.tif', '.tiff', '.gif', '.rgba']
+"""File extensions we understand. """
+
+
+BITMAP_DESCRIPTIONS = [
+    'Bitmap',
+    'Portable Network Graphics',
+    'JPEG',
+    'JPEG',
+    'TIFF',
+    'TIFF',
+    'Graphics Interchange Format',
+    'Raw RGBA']
+"""A description for each :attr:`BITMAP_EXTENSION`. """
+
+
+class Bitmap(object):
+    """The ``Bitmap`` class can be used to load a bitmap image. The
+    :meth:`asImage` method will convert the bitmap into an :class:`.Image`
+    instance.
+    """
+
+    def __init__(self, bmp):
+        """Create a ``Bitmap``.
+
+        :arg bmp: File name of an image, or a ``numpy`` array containing image
+                  data.
+        """
+
+        try:
+            import PIL.Image as Image
+        except ImportError:
+            raise RuntimeError('Install Pillow to use the Bitmap class')
+
+        if isinstance(bmp, six.string_types):
+            source = bmp
+            data   = np.array(Image.open(source))
+
+        elif isinstance(bmp, np.ndarray):
+            source = 'array'
+            data   = np.copy(bmp)
+
+        else:
+            raise ValueError('unknown bitmap: {}'.format(bmp))
+
+        # Make the array (w, h, c)
+        data = np.fliplr(data.transpose((1, 0, 2)))
+        data = np.array(data, dtype=np.uint8, order='C')
+        w, h = data.shape[:2]
+
+        self.__data       = data
+        self.__dataSource = source
+        self.__name       = op.basename(source)
+
+
+    def __hash__(self):
+        """Returns a number which uniquely idenfities this ``Bitmap`` instance
+        (the result of ``id(self)``).
+        """
+        return id(self)
+
+
+    def __str__(self):
+        """Return a string representation of this ``Bitmap`` instance."""
+        return '{}({}, {})'.format(self.__class__.__name__,
+                                   self.dataSource,
+                                   self.shape)
+
+
+    def __repr__(self):
+        """See the :meth:`__str__` method. """
+        return self.__str__()
+
+
+    @property
+    def name(self):
+        """Returns the name of this ``Bitmap``, typically the base name of the
+        file.
+        """
+        return self.__name
+
+
+    @property
+    def dataSource(self):
+        """Returns the bitmap data source - typically the file name. """
+        return self.__dataSource
+
+
+    @property
+    def data(self):
+        """Convenience method which returns the bitmap data as a ``(w, h, c)``
+        array, where ``c`` is either 3 or 4.
+        """
+        return self.__data
+
+
+    @property
+    def shape(self):
+        """Returns the bitmap shape - ``(width, height, nchannels)``. """
+        return self.__data.shape
+
+
+    def asImage(self):
+        """Convert this ``Bitmap`` into an :class:`.Image` instance. """
+
+        width, height, nchannels = self.shape
+
+        if nchannels == 1:
+            dtype = np.uint8
+
+
+        elif nchannels == 3:
+            dtype = np.dtype([('R', 'uint8'),
+                              ('G', 'uint8'),
+                              ('B', 'uint8')])
+
+        elif nchannels == 4:
+            dtype = np.dtype([('R', 'uint8'),
+                              ('G', 'uint8'),
+                              ('B', 'uint8'),
+                              ('A', 'uint8')])
+
+        else:
+            raise ValueError('Cannot convert bitmap with {} '
+                             'channels into nifti image'.format(nchannels))
+
+        if nchannels == 1:
+            data = self.data.reshape((width, height))
+
+        else:
+            data = np.zeros((width, height), dtype=dtype)
+            for ci, ch in enumerate(dtype.names):
+                data[ch] = self.data[..., ci]
+
+        data = np.array(data, order='F', copy=False)
+
+        return fslimage.Image(data, name=self.name)
diff --git a/fsl/data/gifti.py b/fsl/data/gifti.py
index aa8b20ed7b8cedf7a168c901458f0aa85e8d770e..2d1f3856d5ddae797b271a4cf79c4b14a12faf8b 100644
--- a/fsl/data/gifti.py
+++ b/fsl/data/gifti.py
@@ -138,7 +138,10 @@ class GiftiMesh(fslmesh.Mesh):
 
         surfimg, _, vertices, _ = loadGiftiMesh(infile)
 
-        vertices = self.addVertices(vertices, key, *args, **kwargs)
+        for i, v in enumerate(vertices):
+            if i == 0: key = infile
+            else:      key = '{}_{}'.format(infile, i)
+            vertices[i] = self.addVertices(v, key, *args, **kwargs)
 
         self.setMeta(infile, surfimg)
 
diff --git a/fsl/data/image.py b/fsl/data/image.py
index 651992c4dbc3401a5af75ee3e4b59ea2f153fc8e..417a0445ceccd64f6bcc28d7abc1d2214c2de32d 100644
--- a/fsl/data/image.py
+++ b/fsl/data/image.py
@@ -1025,6 +1025,18 @@ class Image(Nifti):
         return self[tuple(coords)].dtype
 
 
+    @property
+    def nvals(self):
+        """Returns the number of values per voxel in this image. This will
+        usually be 1, but may be 3 or 4, for images of type
+        ``NIFTI_TYPE_RGB24`` or ``NIFTI_TYPE_RGBA32``.
+        """
+
+        nvals = len(self.dtype)
+        if nvals == 0: return 1
+        else:          return nvals
+
+
     @Nifti.voxToWorldMat.setter
     def voxToWorldMat(self, xform):
         """Overrides the :meth:`Nifti.voxToWorldMat` property setter.
diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py
index 2b49e4af594268f49a48a5dc280c323bc45d8eda..8afcccacf7ad3e9b4e6ce035aabffa6393d4cf70 100644
--- a/fsl/data/imagewrapper.py
+++ b/fsl/data/imagewrapper.py
@@ -190,10 +190,10 @@ class ImageWrapper(notifier.Notifier):
             if d == 1: self.__numRealDims -= 1
             else:      break
 
-        # Degenerate case - if every
-        # dimension has length 1
-        if self.__numRealDims == 0:
-            self.__numRealDims = len(image.shape)
+        # Degenerate case - less
+        # than three real dimensions
+        if self.__numRealDims < 3:
+            self.__numRealDims = min(3, len(image.shape))
 
         # And save the number of
         # 'padding' dimensions too.
@@ -303,9 +303,11 @@ class ImageWrapper(notifier.Notifier):
         # data range for each volume/slice/vector
         #
         # We use nan as a placeholder, so the
-        # dtype must be non-integral
+        # dtype must be non-integral. The
+        # len(dtype) check takes into account
+        # structured data (e.g. RGB)
         dtype = self.__image.get_data_dtype()
-        if np.issubdtype(dtype, np.integer):
+        if np.issubdtype(dtype, np.integer) or len(dtype) > 0:
             dtype = np.float32
         self.__volRanges = np.zeros((nvols, 2),
                                     dtype=dtype)
@@ -703,7 +705,7 @@ class ImageWrapper(notifier.Notifier):
                     raise IndexError('Invalid assignment: [{}] = {}'.format(
                         sliceobj, len(values)))
 
-                values = values[0]
+                values = np.array(values).flatten()[0]
 
             # Make sure that the values
             # have a compatible shape.
diff --git a/fsl/data/utils.py b/fsl/data/utils.py
index 13aae313b3ea4d1dc927d7b1fffd029ccaf7f83c..690a1ac5f7be488ba09eec913b09d5ee88148c75 100644
--- a/fsl/data/utils.py
+++ b/fsl/data/utils.py
@@ -27,6 +27,7 @@ def guessType(path):
     import fsl.data.gifti           as fslgifti
     import fsl.data.freesurfer      as fslfs
     import fsl.data.mghimage        as fslmgh
+    import fsl.data.bitmap          as fslbmp
     import fsl.data.featimage       as featimage
     import fsl.data.melodicimage    as melimage
     import fsl.data.dtifit          as dtifit
@@ -48,14 +49,16 @@ def guessType(path):
     if op.isfile(path):
 
         # Some types are easy - just check the extensions
-        if fslpath.hasExt(path, fslvtk.ALLOWED_EXTENSIONS):
+        if fslpath.hasExt(path.lower(), fslvtk.ALLOWED_EXTENSIONS):
             return fslvtk.VTKMesh, path
-        elif fslpath.hasExt(path, fslgifti.ALLOWED_EXTENSIONS):
+        elif fslpath.hasExt(path.lower(), fslgifti.ALLOWED_EXTENSIONS):
             return fslgifti.GiftiMesh, path
         elif fslfs.isGeometryFile(path):
             return fslfs.FreesurferMesh, path
-        elif fslpath.hasExt(path, fslmgh.ALLOWED_EXTENSIONS):
+        elif fslpath.hasExt(path.lower(), fslmgh.ALLOWED_EXTENSIONS):
             return fslmgh.MGHImage, path
+        elif fslpath.hasExt(path.lower(), fslbmp.BITMAP_EXTENSIONS):
+            return fslbmp.Bitmap, path
 
         # Other specialised image types
         elif melanalysis .isMelodicImage(path):
diff --git a/fsl/utils/naninfrange.py b/fsl/utils/naninfrange.py
index c8437c8d9b10ec8b69b6d6bb54d5292314907c17..bcdf5b12cffa1cc80bc1b3d09d8cc7f141640bef 100644
--- a/fsl/utils/naninfrange.py
+++ b/fsl/utils/naninfrange.py
@@ -23,6 +23,30 @@ def naninfrange(data):
     use an alternate approach to calculating the minimum/maximum.
     """
 
+    # For structured arrays, we assume that
+    # all fields have the same dtype, and we
+    # simply take the range across all fields
+    if len(data.dtype) > 0:
+
+        # Avoid inducing a data copy if
+        # at all possible. np.ndarray
+        # doesn't preserve the underlying
+        # order, so let's set that. Also,
+        # we're forced to make a copy if
+        # the array is not contiguous,
+        # otherwise ndarray will complain
+        if   data.flags['C_CONTIGUOUS']: order = 'C'
+        elif data.flags['F_CONTIGUOUS']: order = 'F'
+        else:
+            data  = np.ascontiguousarray(data)
+            order = 'C'
+
+        shape = [len(data.dtype)] + list(data.shape)
+        data  = np.ndarray(buffer=data.data,
+                           shape=shape,
+                           order=order,
+                           dtype=data.dtype[0])
+
     if not np.issubdtype(data.dtype, np.floating):
         return data.min(), data.max()
 
diff --git a/fsl/version.py b/fsl/version.py
index 520dc6191809b5bf4f618a4cb41b79d43f1f958a..f8e6092135fbacea0d5a152c82de9ab93650fde5 100644
--- a/fsl/version.py
+++ b/fsl/version.py
@@ -47,7 +47,7 @@ import            re
 import            string
 
 
-__version__ = '2.3.0.dev0'
+__version__ = '2.4.0.dev0'
 """Current version number, as a string. """
 
 
diff --git a/requirements-extra.txt b/requirements-extra.txt
index 774efb03f31ee244da45acd703238d60c02191b8..333cde7a982fd7502eec2af60e26b87b332dac0f 100644
--- a/requirements-extra.txt
+++ b/requirements-extra.txt
@@ -2,3 +2,4 @@ indexed_gzip>=0.7.0
 wxpython==4.*
 trimesh>=2.37.29
 rtree==0.8.3
+Pillow>=3.2.0
diff --git a/setup.cfg b/setup.cfg
index e82f3b4445a47f24679fc30bd1f339fdcb5e4832..0c604f57e04cd74faf4721accfb0353964f80ebf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -11,6 +11,7 @@ universal=1
 #   - dicomtest:  Requires dcm2niix
 #   - meshtest:   Requires trimesh and rtree
 #   - igziptest:  Requires indexed_gzip
+#   - piltest:    Requires Pillow
 #   - noroottest: Need to be executed as
 #                 non-root user (will fail
 #                 otherwise)
diff --git a/tests/test_bitmap.py b/tests/test_bitmap.py
new file mode 100644
index 0000000000000000000000000000000000000000..b59c4083e2e822400f19ccc6895d500179fb4149
--- /dev/null
+++ b/tests/test_bitmap.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# test_bitmap.py -
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+
+
+import numpy as np
+
+import pytest
+
+import fsl.utils.tempdir as tempdir
+import fsl.data.bitmap   as fslbmp
+
+
+@pytest.mark.piltest
+def test_bitmap():
+
+    from PIL import Image
+
+    with tempdir.tempdir():
+        data = np.random.randint(0, 255, (100, 200, 4), dtype=np.uint8)
+        img  = Image.fromarray(data, mode='RGBA')
+
+        img.save('image.png')
+
+        bmp = fslbmp.Bitmap('image.png')
+
+        assert bmp.name       == 'image.png'
+        assert bmp.dataSource == 'image.png'
+        assert bmp.shape      == (200, 100, 4)
+
+        repr(bmp)
+        hash(bmp)
+
+        assert np.all(bmp.data == np.fliplr(data.transpose(1, 0, 2)))
+
+
+@pytest.mark.piltest
+def test_bitmap_asImage():
+    from PIL import Image
+
+    with tempdir.tempdir():
+        d3 = np.random.randint(0, 255, (100, 200, 3), dtype=np.uint8)
+        d4 = np.random.randint(0, 255, (100, 200, 4), dtype=np.uint8)
+
+        img3 = Image.fromarray(d3, mode='RGB')
+        img4 = Image.fromarray(d4, mode='RGBA')
+
+        img3.save('rgb.png')
+        img4.save('rgba.png')
+
+        bmp3  = fslbmp.Bitmap('rgb.png')
+        bmp4  = fslbmp.Bitmap('rgba.png')
+
+        i3 = bmp3.asImage()
+        i4 = bmp4.asImage()
+
+        assert i3.shape == (200, 100, 1)
+        assert i4.shape == (200, 100, 1)
+        assert i3.nvals == 3
+        assert i4.nvals == 4
diff --git a/tests/test_gifti.py b/tests/test_gifti.py
index 7a8ff6175658ed377ee4bc2a9ea08030f96a8834..835655b1e7da240484101ec7aa4a2b44dbf7490e 100644
--- a/tests/test_gifti.py
+++ b/tests/test_gifti.py
@@ -288,24 +288,38 @@ def test_GiftiMesh_multiple_vertices():
     verts1 = TEST_VERT_ARRAY
     verts2 = nib.gifti.GiftiDataArray(
         TEST_VERTS * 5, intent='NIFTI_INTENT_POINTSET')
+    verts3 = nib.gifti.GiftiDataArray(
+        TEST_VERTS * 10, intent='NIFTI_INTENT_POINTSET')
 
     gimg  = nib.gifti.GiftiImage(darrays=[verts1, verts2, tris])
+    gimg2 = nib.gifti.GiftiImage(darrays=[verts3, tris])
 
     with tempdir():
-        fname = op.abspath('test.gii')
-        gimg.to_filename(fname)
+        fname  = op.abspath('test.gii')
+        fname2 = op.abspath('test2.gii')
+        gimg .to_filename(fname)
+        gimg2.to_filename(fname2)
+
         surf  = gifti.GiftiMesh(fname)
 
-        expvsets = [fname,
-                    '{}_1'.format(fname)]
+        expvsets = [fname, '{}_1'.format(fname)]
+
+        expbounds1 = np.min(verts1.data, axis=0), np.max(verts1.data, axis=0)
+        expbounds2 = np.min(verts2.data, axis=0), np.max(verts2.data, axis=0)
+        expbounds3 = np.min(verts3.data, axis=0), np.max(verts3.data, axis=0)
 
         assert np.all(surf.vertices == TEST_VERTS)
         assert np.all(surf.indices  == TEST_IDXS)
         assert  surf.vertexSets()   == expvsets
+        assert np.all(np.isclose(surf.bounds, expbounds1))
 
         surf.vertices = expvsets[1]
-
         assert np.all(surf.vertices == TEST_VERTS * 5)
+        assert np.all(np.isclose(surf.bounds, expbounds2))
+
+        surf.loadVertices(fname2, select=True)
+        assert np.all(surf.vertices == TEST_VERTS * 10)
+        assert np.all(np.isclose(surf.bounds, expbounds3))
 
 
 def test_GiftiMesh_needsFixing():
diff --git a/tests/test_image.py b/tests/test_image.py
index 85dde7c66d44dd76bc137856f78de72163df2d39..5b1ba4c7ff15c2e7573fd2f577108fc7399fc23e 100644
--- a/tests/test_image.py
+++ b/tests/test_image.py
@@ -306,6 +306,7 @@ def _test_Image_atts(imgtype):
             assert tuple(i.nibImage.shape)              == tuple(dims)
             assert tuple(i.nibImage.header.get_zooms()) == tuple(pixdims)
 
+            assert i.nvals      == 1
             assert i.ndim       == expndims
             assert i.dtype      == dtype
             assert i.name       == op.basename(path)
@@ -1147,3 +1148,28 @@ def _test_Image_init_xform(imgtype):
         del fimg
         del img
         img = None
+
+
+def test_rgb_image():
+    with tempdir():
+
+        dtype = np.dtype([('R', 'uint8'),
+                          ('G', 'uint8'),
+                          ('B', 'uint8')])
+        data  = np.zeros((20, 20, 20), dtype=dtype)
+
+        for i in np.ndindex(data.shape):
+            data['R'][i] = np.random.randint(0,   100)
+            data['G'][i] = np.random.randint(100, 200)
+            data['B'][i] = np.random.randint(200, 256)
+
+        # fix the data limits
+        data['R'][0, 0, 0] = 0
+        data['B'][0, 0, 0] = 255
+
+        nib.Nifti1Image(data, np.eye(4)).to_filename('rgb.nii')
+
+        img = fslimage.Image('rgb.nii')
+
+        assert img.nvals     == 3
+        assert img.dataRange == (0, 255)
diff --git a/tests/test_imagewrapper.py b/tests/test_imagewrapper.py
index 65c790ce3f41300b13cc1dfec07b6f93c8a2e0dd..fc78da8460f3f1d7ed20364206d3ef12cbae0934 100644
--- a/tests/test_imagewrapper.py
+++ b/tests/test_imagewrapper.py
@@ -1173,8 +1173,10 @@ def test_3D_indexing(shape=None, img=None):
 
     # Test that a 3D image looks like a 3D image
 
-    if   shape is None:   shape = (21, 22, 23)
-    elif len(shape) == 2: shape = tuple(list(shape) + [1])
+    if   shape is None:
+        shape = (21, 22, 23)
+    elif len(shape) < 3:
+        shape = tuple(list(shape) + [1] * (3 - len(shape)))
 
     if img is None:
         data   = np.random.random(shape)
@@ -1264,8 +1266,10 @@ def test_3D_len_one_indexing(shape=None, img=None):
     # look like a 3D image, but should still
     # accept (valid) 2D slicing.
 
-    if   shape is None:  shape = (20, 20, 1)
-    elif len(shape) < 3: shape = tuple(list(shape) + [1])
+    if shape is None:
+        shape = (20, 20, 1)
+    elif len(shape) < 3:
+        shape = tuple(list(shape) + [1] * (3 - len(shape)))
 
     if img is None:
         data   = np.random.random(shape)
@@ -1305,6 +1309,20 @@ def test_2D_indexing():
     test_3D_len_one_indexing(shape, img)
 
 
+def test_1D_indexing():
+
+    # Testing ImageWrapper for a 1D image -
+    # it should look just like a 3D image
+    # (the same as is tested above).
+
+    shape  = (20,)
+    data   = np.random.random(shape)
+    nibImg = nib.Nifti1Image(data, np.eye(4))
+    img    = imagewrap.ImageWrapper(nibImg, loadData=True)
+
+    test_3D_len_one_indexing(shape, img)
+
+
 def test_4D_indexing(shape=None, img=None):
 
     if shape is None:
diff --git a/tests/test_naninfrange.py b/tests/test_naninfrange.py
index dee79948a1a4021ab6f0e3fa72a84e64319e5b4d..0c232d0085f65378a70dce3c37ee95147280034d 100644
--- a/tests/test_naninfrange.py
+++ b/tests/test_naninfrange.py
@@ -50,3 +50,44 @@ def test_naninfrange():
         if   np.isfinite(expected[1]): assert result[1] == expected[1]
         elif np.isnan(   expected[1]): assert np.isnan(result[1])
         elif np.isinf(   expected[1]): assert np.isinf(result[1])
+
+
+def test_naninfrange_structured_ordered_contiguous():
+
+    data = np.random.random((4, 5, 6))
+
+    cdata = data.copy(order='C')
+    fdata = data.copy(order='F')
+
+    sdtype = np.dtype([('R', 'float64'), ('G', 'float64'), ('B', 'float64')])
+    sdata = np.zeros(data.shape, dtype=sdtype)
+    sdata['R'] = data
+    sdata['G'] = data
+    sdata['B'] = data
+
+    csdata = sdata.copy(order='C')
+    fsdata = sdata.copy(order='F')
+
+    tests = [
+        cdata,
+        cdata.transpose(1, 0, 2),
+        cdata[2:4, 1:3, 0:4],
+        fdata,
+        fdata.transpose(1, 0, 2),
+        fdata[2:4, 1:3, 0:4],
+        csdata,
+        csdata.transpose(1, 0, 2),
+        csdata[2:4, 1:3, 0:4],
+        fsdata,
+        fsdata.transpose(1, 0, 2),
+        fsdata[2:4, 1:3, 0:4]
+    ]
+
+    for t in tests:
+        if len(t.dtype) > 0:
+            expmin = np.min([t[n].min() for n in t.dtype.names])
+            expmax = np.max([t[n].max() for n in t.dtype.names])
+        else:
+            expmin, expmax = np.min(t), np.max(t)
+        result = naninfrange.naninfrange(t)
+        assert np.all(np.isclose(result, (expmin, expmax)))