Repository: imageio/imageio

Issue:
Support writing 16-bit color channels for PNG
I'm trying to transition away from MATLAB and imageio has been incredibly helpful. However, I need to work with 64-bit PNGs (16-bit color channels and 16-bit alpha channel) and although it looks like reading 16-bit channels is supported, writing is not supported.

I created some 64-bit pngs in MATLAB using the following code:

    img_out = zeros(256, 256, 3, 'uint16');
    img_alpha = zeros(256, 256, 'uint16');
    color_grad = uint16(reshape(0:2^16-1, 256, []));
    img_out(:, :, 1) = color_grad;
    img_out(:, :, 2) = rot90(color_grad, 1);
    img_out(:, :, 3) = rot90(color_grad, 2);
    img_out_alpha = rot90(color_grad, 3);

    fprintf('Write unique values: R=%u, G=%u, B=%u, A=%u\n', ...
            length(unique(img_out(:, :, 1))), ...
            length(unique(img_out(:, :, 2))), ...
            length(unique(img_out(:, :, 3))), ...
            length(unique(img_out_alpha)))
    imwrite(img_out, '64bit_MATLAB.png', 'Alpha', img_out_alpha, 'BitDepth', 16)
    
    [img_in, ~, img_in_alpha] = imread('64bit_MATLAB.png');
    fprintf('MATLAB PNG unique values: R=%u, G=%u, B=%u, A=%u\n', ...
            length(unique(img_in(:, :, 1))), ...
            length(unique(img_in(:, :, 2))), ...
            length(unique(img_in(:, :, 3))), ...
            length(unique(img_in_alpha)))

This gives me the following output:

> Write unique values: R=65536, G=65536, B=65536, A=65536
> MATLAB PNG unique values: R=65536, G=65536, B=65536, A=65536

If I try the same with imageio in Python:

    import imageio
    import numpy as np

    img_out = np.zeros((256, 256, 4), dtype=np.uint16)
    color_grad = np.reshape(np.arange(2**16), (256,-1))
    img_out[:, :, 0] = color_grad
    img_out[:, :, 1] = np.rot90(color_grad, 1)
    img_out[:, :, 2] = np.rot90(color_grad, 2)
    img_out[:, :, 3] = np.rot90(color_grad, 3)

    print('Write unique values: R={}, G={}, B={}, A={}'.format(
        len(set(img_out[:, :, 0].flatten().tolist())),
        len(set(img_out[:, :, 1].flatten().tolist())),
        len(set(img_out[:, :, 2].flatten().tolist())),
        len(set(img_out[:, :, 3].flatten().tolist()))))
    imageio.imwrite('64bit_imageio.png', img_out)

    img_in = imageio.imread('64bit_imageio.png')
    print('imageio PNG unique values: R={}, G={}, B={}, A={}'.format(
        len(set(img_in[:, :, 0].flatten().tolist())),
        len(set(img_in[:, :, 1].flatten().tolist())),
        len(set(img_in[:, :, 2].flatten().tolist())),
        len(set(img_in[:, :, 3].flatten().tolist()))))

I get the following:

> Write unique values: R=65536, G=65536, B=65536, A=65536
> imageio PNG unique values: R=256, G=256, B=256, A=256

To confirm, imageio is able to read the 64-bit PNG:

    img_in = imageio.imread('64bit_MATLAB.png')
    print('MATLAB PNG unique values: R={}, G={}, B={}, A={}'.format(
        len(set(img_in[:, :, 0].flatten().tolist())),
        len(set(img_in[:, :, 1].flatten().tolist())),
        len(set(img_in[:, :, 2].flatten().tolist())),
        len(set(img_in[:, :, 3].flatten().tolist()))))

> MATLAB PNG unique values: R=65536, G=65536, B=65536, A=65536

Any idea how difficult this would be to implement?

Discussion:

Patch:
diff --git a/imageio/core/__init__.py b/imageio/core/__init__.py
index 0347ec7..b5cedf0 100644
--- a/imageio/core/__init__.py
+++ b/imageio/core/__init__.py
@@ -6,7 +6,7 @@
 (everything but the plugins).
 """
 
-from .util import Image, Dict, asarray, image_as_uint8, urlopen  # noqa
+from .util import Image, Dict, asarray, image_as_uint, 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, appdata_dir, resource_dirs, has_module  # noqa
diff --git a/imageio/core/util.py b/imageio/core/util.py
index 12463c2..70d0b99 100644
--- a/imageio/core/util.py
+++ b/imageio/core/util.py
@@ -13,6 +13,7 @@ import os
 import sys
 import time
 import struct
+from warnings import warn
 
 import numpy as np
 
@@ -46,39 +47,71 @@ 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.
+def image_as_uint(im, bitdepth=None):
+    """ Convert the given image to uint (default: uint8)
+    
+    If the dtype already matches the desired format, it is returned
+    as-is. If the image is float, and all values are between 0 and 1,
+    the values are multiplied by np.power(2.0, bitdepth). In all other
+    situations, the values are scaled such that the minimum value
+    becomes 0 and the maximum value becomes np.power(2.0, bitdepth)-1
+    (255 for 8-bit and 65535 for 16-bit).
     """
+    if not bitdepth:
+        bitdepth = 8
     if not isinstance(im, np.ndarray):
-        raise ValueError('image must be a numpy array')
+        raise ValueError('Image must be a numpy array')
+    if bitdepth == 8:
+        out_type = np.uint8
+    elif bitdepth == 16:
+        out_type = np.uint16
+    else:
+        raise ValueError('Bitdepth must be either 8 or 16')
     dtype_str = str(im.dtype)
-    # Already uint8?
-    if dtype_str == 'uint8':
+    if ((im.dtype == np.uint8 and bitdepth == 8) or
+       (im.dtype == np.uint16 and bitdepth == 16)):
+        # Already the correct format? Return as-is
         return im
-    # 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)
+    if (dtype_str.startswith('float') and
+       np.nanmin(im) >= 0 and np.nanmax(im) <= 1):
+        warn('Lossy conversion from {0} to {1}, range [0, 1]'.format(
+             dtype_str, out_type.__name__))
+        im = im.astype(np.float64) * (np.power(2.0, bitdepth)-1)
+    elif im.dtype == np.uint16 and bitdepth == 8:
+        warn('Lossy conversion from uint16 to uint8, '
+             'loosing 8 bits of resolution')
+        im = np.right_shift(im, 8)
+    elif im.dtype == np.uint32:
+        warn('Lossy conversion from uint32 to {0}, '
+             'loosing {1} bits of resolution'.format(out_type.__name__,
+                                                     32-bitdepth))
+        im = np.right_shift(im, 32-bitdepth)
+    elif im.dtype == np.uint64:
+        warn('Lossy conversion from uint64 to {0}, '
+             'loosing {1} bits of resolution'.format(out_type.__name__,
+                                                     64-bitdepth))
+        im = np.right_shift(im, 64-bitdepth)
+    else:
+        mi = np.nanmin(im)
+        ma = np.nanmax(im)
+        if not np.isfinite(mi):
+            raise ValueError('Minimum image value is not finite')
+        if not np.isfinite(ma):
+            raise ValueError('Maximum image value is not finite')
+        if ma == mi:
+            raise ValueError('Max value == min value, ambiguous given dtype')
+        warn('Conversion from {0} to {1}, '
+             'range [{2}, {3}]'.format(dtype_str, out_type.__name__, mi, ma))
+        # Now make float copy before we scale
+        im = im.astype('float64')
+        # Scale the values between 0 and 1 then multiply by the max value
+        im = (im - mi) / (ma - mi) * (np.power(2.0, bitdepth)-1)
+    assert np.nanmin(im) >= 0
+    assert np.nanmax(im) < np.power(2.0, bitdepth)
+    return im.astype(out_type)
 
 
-# currently not used ... the only use it to easly provide the global meta info
+# currently not used ... the only use it to easily provide the global meta info
 class ImageList(list):
     def __init__(self, meta=None):
         list.__init__(self)
diff --git a/imageio/plugins/_freeimage.py b/imageio/plugins/_freeimage.py
index b6541c7..90c9310 100644
--- a/imageio/plugins/_freeimage.py
+++ b/imageio/plugins/_freeimage.py
@@ -559,16 +559,15 @@ class Freeimage(object):
             
             # Test if ok
             if ftype == -1:
-                raise ValueError('Cannot determine format of file "%s"' % 
+                raise ValueError('Cannot determine format of file "%s"' %
                                  filename)
             elif mode == 'w' and not lib.FreeImage_FIFSupportsWriting(ftype):
-                raise ValueError('Cannot write the format of file "%s"' % 
+                raise ValueError('Cannot write the format of file "%s"' %
                                  filename)
             elif mode == 'r' and not lib.FreeImage_FIFSupportsReading(ftype):
-                raise ValueError('Cannot read the format of file "%s"' % 
+                raise ValueError('Cannot read the format of file "%s"' %
                                  filename)
-            else:
-                return ftype
+            return ftype
     
     def create_bitmap(self, filename, ftype, flags=0):
         """ create_bitmap(filename, ftype, flags=0)
@@ -796,8 +795,7 @@ class FIBitmap(FIBaseBitmap):
             if not bitmap:  # pragma: no cover
                 raise RuntimeError('Could not allocate bitmap for storage: %s'
                                    % self._fi._get_error_message())
-            else:
-                self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
+            self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
     
     def load_from_filename(self, filename=None):
         if filename is None:
@@ -814,8 +812,7 @@ class FIBitmap(FIBaseBitmap):
                 raise ValueError('Could not load bitmap "%s": %s' % 
                                  (self._filename, 
                                   self._fi._get_error_message()))
-            else:
-                self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
+            self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
     
 # def load_from_bytes(self, bytes):
 #     with self._fi as lib: 
@@ -1144,12 +1141,12 @@ class FIBitmap(FIBaseBitmap):
                 raise ValueError('Could not quantize bitmap "%s": %s' % 
                                  (self._filename, 
                                   self._fi._get_error_message()))
-            else:
-                new = FIBitmap(self._fi, self._filename, self._ftype, 
-                               self._flags)
-                new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
-                new._fi_type = self._fi_type
-                return new
+
+            new = FIBitmap(self._fi, self._filename, self._ftype,
+                           self._flags)
+            new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
+            new._fi_type = self._fi_type
+            return new
     
 # def convert_to_32bit(self):
 #     """ Convert to 32bit image.
@@ -1201,9 +1198,8 @@ class FIMultipageBitmap(FIBaseBitmap):
                 err = self._fi._get_error_message()
                 raise ValueError('Could not open file "%s" as multi-image: %s'
                                  % (self._filename, err))
-            else:
-                self._set_bitmap(multibitmap, 
-                                 (lib.FreeImage_CloseMultiBitmap, multibitmap))
+            self._set_bitmap(multibitmap,
+                             (lib.FreeImage_CloseMultiBitmap, multibitmap))
 
 # def load_from_bytes(self, bytes):
 #     with self._fi as lib:
@@ -1248,9 +1244,8 @@ class FIMultipageBitmap(FIBaseBitmap):
                 msg = ('Could not open file "%s" for writing multi-image: %s' 
                        % (self._filename, self._fi._get_error_message()))
                 raise ValueError(msg)
-            else:
-                self._set_bitmap(multibitmap, 
-                                 (lib.FreeImage_CloseMultiBitmap, multibitmap))
+            self._set_bitmap(multibitmap,
+                             (lib.FreeImage_CloseMultiBitmap, multibitmap))
     
     def __len__(self):
         with self._fi as lib:
diff --git a/imageio/plugins/ffmpeg.py b/imageio/plugins/ffmpeg.py
index 8356c5c..0e08670 100644
--- a/imageio/plugins/ffmpeg.py
+++ b/imageio/plugins/ffmpeg.py
@@ -25,7 +25,7 @@ import numpy as np
 
 from .. import formats
 from ..core import (Format, get_remote_file, string_types, read_n_bytes,
-                    image_as_uint8, get_platform, InternetNotAllowedError)
+                    image_as_uint, get_platform, InternetNotAllowedError)
 
 FNAME_PER_PLATFORM = {
     'osx32': 'ffmpeg.osx',
@@ -575,7 +575,7 @@ class FfmpegFormat(Format):
             depth = 1 if im.ndim == 2 else im.shape[2]
 
             # Ensure that image is in uint8
-            im = image_as_uint8(im)
+            im = image_as_uint(im, bitdepth=8)
 
             # Set size and initialize if not initialized yet
             if self._size is None:
diff --git a/imageio/plugins/freeimage.py b/imageio/plugins/freeimage.py
index 818ba39..af0b242 100644
--- a/imageio/plugins/freeimage.py
+++ b/imageio/plugins/freeimage.py
@@ -11,9 +11,8 @@ from __future__ import absolute_import, print_function, division
 
 import numpy as np
 
-
 from .. import formats
-from ..core import Format, image_as_uint8
+from ..core import Format, image_as_uint
 from ._freeimage import fi, IO_FLAGS, FNAME_PER_PLATFORM  # noqa
 
 
@@ -165,7 +164,7 @@ class FreeimageBmpFormat(FreeimageFormat):
             return FreeimageFormat.Writer._open(self, flags)
         
         def _append_data(self, im, meta):
-            im = image_as_uint8(im)
+            im = image_as_uint(im, bitdepth=8)
             return FreeimageFormat.Writer._append_data(self, im, meta)
 
 
@@ -222,7 +221,10 @@ class FreeimagePngFormat(FreeimageFormat):
             return FreeimageFormat.Writer._open(self, flags)
         
         def _append_data(self, im, meta):
-            im = image_as_uint8(im)
+            if str(im.dtype) == 'uint16':
+                im = image_as_uint(im, bitdepth=16)
+            else:
+                im = image_as_uint(im, bitdepth=8)
             FreeimageFormat.Writer._append_data(self, im, meta)
             # Quantize?
             q = int(self.request.kwargs.get('quantize', False))
@@ -335,7 +337,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.')
-            im = image_as_uint8(im)
+            im = image_as_uint(im, bitdepth=8)
             return FreeimageFormat.Writer._append_data(self, im, meta)
 
 
diff --git a/imageio/plugins/freeimagemulti.py b/imageio/plugins/freeimagemulti.py
index 1d0fd1f..1e7b6e7 100644
--- a/imageio/plugins/freeimagemulti.py
+++ b/imageio/plugins/freeimagemulti.py
@@ -10,7 +10,7 @@ from __future__ import absolute_import, print_function, division
 import numpy as np
 
 from .. import formats
-from ..core import Format, image_as_uint8
+from ..core import Format, image_as_uint
 from ._freeimage import fi, IO_FLAGS
 from .freeimage import FreeimageFormat
 
@@ -73,7 +73,7 @@ class FreeimageMulti(FreeimageFormat):
             # Prepare data
             if im.ndim == 3 and im.shape[-1] == 1:
                 im = im[:, :, 0]
-            im = image_as_uint8(im)
+            im = image_as_uint(im, bitdepth=8)
             # Create sub bitmap
             sub1 = fi.create_bitmap(self._bm._filename, self.format.fif)
             # Let subclass add data to bitmap, optionally return new
diff --git a/imageio/plugins/swf.py b/imageio/plugins/swf.py
index df7cd78..2006524 100644
--- a/imageio/plugins/swf.py
+++ b/imageio/plugins/swf.py
@@ -14,7 +14,7 @@ from io import BytesIO
 import numpy as np
 
 from .. import formats
-from ..core import Format, read_n_bytes, image_as_uint8
+from ..core import Format, read_n_bytes, image_as_uint
 
 
 _swf = None  # lazily loaded in lib()
@@ -301,7 +301,7 @@ class SWFFormat(Format):
             # Correct shape and type
             if im.ndim == 3 and im.shape[-1] == 1:
                 im = im[:, :, 0]
-            im = image_as_uint8(im)
+            im = image_as_uint(im, bitdepth=8)
             # Get frame size
             wh = im.shape[1], im.shape[0]
             # Write header on first frame


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 1f35e77..8425930 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -738,34 +738,65 @@ def test_util_progres_bar(sleep=0):
             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_util_image_as_uint():
+    ''' Tests the various type conversions when writing to uint'''
+    raises(ValueError, core.image_as_uint, 4)
+    raises(ValueError, core.image_as_uint, "not an image")
+    raises(ValueError, core.image_as_uint, np.array([0, 1]), bitdepth=13)
+    raises(ValueError, core.image_as_uint, np.array([2.0, 2.0], 'float32'))
+    raises(ValueError, core.image_as_uint, np.array([0.0, np.inf], 'float32'))
+    raises(ValueError, core.image_as_uint, np.array([-np.inf, 0.0], 'float32'))
+
+    test_arrays = (  # (input, output bitdepth, expected output)
+        # No bitdepth specified, assumed to be 8-bit
+        (np.array([0, 2 ** 8 - 1], 'uint8'), None, np.uint8([0, 255])),
+        (np.array([0, 2 ** 16 - 1], 'uint16'), None, np.uint8([0, 255])),
+        (np.array([0, 2 ** 32 - 1], 'uint32'), None, np.uint8([0, 255])),
+        (np.array([0, 2 ** 64 - 1], 'uint64'), None, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int8'), None, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int16'), None, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int32'), None, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int64'), None, np.uint8([0, 255])),
+        (np.array([0, 1], 'float16'), None, np.uint8([0, 255])),
+        (np.array([0, 1], 'float32'), None, np.uint8([0, 255])),
+        (np.array([0, 1], 'float64'), None, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float16'), None, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float32'), None, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float64'), None, np.uint8([0, 255])),
+        # 8-bit output
+        (np.array([0, 2 ** 8 - 1], 'uint8'), 8, np.uint8([0, 255])),
+        (np.array([0, 2 ** 16 - 1], 'uint16'), 8, np.uint8([0, 255])),
+        (np.array([0, 2 ** 32 - 1], 'uint32'), 8, np.uint8([0, 255])),
+        (np.array([0, 2 ** 64 - 1], 'uint64'), 8, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int8'), 8, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int16'), 8, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int32'), 8, np.uint8([0, 255])),
+        (np.array([-2, 2], 'int64'), 8, np.uint8([0, 255])),
+        (np.array([0, 1], 'float16'), 8, np.uint8([0, 255])),
+        (np.array([0, 1], 'float32'), 8, np.uint8([0, 255])),
+        (np.array([0, 1], 'float64'), 8, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float16'), 8, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float32'), 8, np.uint8([0, 255])),
+        (np.array([-1.0, 1.0], 'float64'), 8, np.uint8([0, 255])),
+        # 16-bit output
+        (np.array([0, 2 ** 8 - 1], 'uint8'), 16, np.uint16([0, 65535])),
+        (np.array([0, 2 ** 16 - 1], 'uint16'), 16, np.uint16([0, 65535])),
+        (np.array([0, 2 ** 32 - 1], 'uint32'), 16, np.uint16([0, 65535])),
+        (np.array([0, 2 ** 64 - 1], 'uint64'), 16, np.uint16([0, 65535])),
+        (np.array([-2, 2], 'int8'), 16, np.uint16([0, 65535])),
+        (np.array([-2, 2], 'int16'), 16, np.uint16([0, 65535])),
+        (np.array([-2, 2], 'int32'), 16, np.uint16([0, 65535])),
+        (np.array([-2, 2], 'int64'), 16, np.uint16([0, 65535])),
+        (np.array([0, 1], 'float16'), 16, np.uint16([0, 65535])),
+        (np.array([0, 1], 'float32'), 16, np.uint16([0, 65535])),
+        (np.array([0, 1], 'float64'), 16, np.uint16([0, 65535])),
+        (np.array([-1.0, 1.0], 'float16'), 16, np.uint16([0, 65535])),
+        (np.array([-1.0, 1.0], 'float32'), 16, np.uint16([0, 65535])),
+        (np.array([-1.0, 1.0], 'float64'), 16, np.uint16([0, 65535])),)
+
+    for tup in test_arrays:
+        res = core.image_as_uint(tup[0], bitdepth=tup[1])
+        assert res[0] == tup[2][0] and res[1] == tup[2][1]
 
 
 def test_util_has_has_module():
diff --git a/tests/test_freeimage.py b/tests/test_freeimage.py
index 6a75fb5..1b5ab83 100644
--- a/tests/test_freeimage.py
+++ b/tests/test_freeimage.py
@@ -37,7 +37,7 @@ im4[20:, :, 3] = 120
 fnamebase = os.path.join(test_dir, 'test')
 
 
-def get_ref_im(colors, crop, float):
+def get_ref_im(colors, crop, isfloat):
     """ Get reference image with
     * colors: 0, 1, 3, 4
     * cropping: 0-> none, 1-> crop, 2-> crop with non-contiguous data
@@ -45,9 +45,9 @@ def get_ref_im(colors, crop, float):
     """
     assert colors in (0, 1, 3, 4)
     assert crop in (0, 1, 2)
-    assert float in (False, True)
+    assert isfloat in (False, True)
     rim = [im0, im1, None, im3, im4][colors]
-    if float:
+    if isfloat:
         rim = rim.astype(np.float32) / 255.0
     if crop == 1:
         rim = rim[:-1, :-1].copy()
@@ -144,27 +144,27 @@ def test_freeimage_lib():
     
 def test_png():
     
-    for float in (False, True):
+    for isfloat 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)
+                fname = fnamebase+'%i.%i.%i.png' % (isfloat, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat)
                 imageio.imsave(fname, rim)
                 im = imageio.imread(fname)
-                mul = 255 if float else 1
+                mul = 255 if isfloat 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 isfloat 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)
+                    fname = fnamebase+'%i.%i.%i.png' % (isfloat, crop, colors)
+                    rim = get_ref_im(colors, crop, isfloat)
                     imageio.imsave(fname, rim)
                     im = imageio.imread(fname)
-                    mul = 255 if float else 1
+                    mul = 255 if isfloat else 1
                     assert_close(rim * mul, im, 0.1)  # lossless
     finally:
         imageio.plugins._freeimage.TEST_NUMPY_NO_STRIDES = False
@@ -240,14 +240,14 @@ def test_png_dtypes():
 
 def test_jpg():
     
-    for float in (False, True):
+    for isfloat 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)
+                fname = fnamebase + '%i.%i.%i.jpg' % (isfloat, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat)
                 imageio.imsave(fname, rim)
                 im = imageio.imread(fname)
-                mul = 255 if float else 1
+                mul = 255 if isfloat else 1
                 assert_close(rim * mul, im, 1.1)  # lossy
     
     # No alpha in JPEG
@@ -303,14 +303,14 @@ def test_jpg_more():
 
 def test_bmp():
     
-    for float in (False, True):
+    for isfloat 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)
+                fname = fnamebase + '%i.%i.%i.bmp' % (isfloat, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat)
                 imageio.imsave(fname, rim)
                 im = imageio.imread(fname)
-                mul = 255 if float else 1
+                mul = 255 if isfloat else 1
                 assert_close(rim * mul, im, 0.1)  # lossless
     
     # Compression
@@ -328,16 +328,16 @@ def test_bmp():
 def test_gif():
     # The not-animated gif
     
-    for float in (False, True):
+    for isfloat 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)
+                fname = fnamebase + '%i.%i.%i.gif' % (isfloat, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat)
                 imageio.imsave(fname, rim)
                 im = imageio.imread(fname)
-                mul = 255 if float else 1
+                mul = 255 if isfloat else 1
                 if colors in (0, 1):
                     im = im[:, :, 0]
                 else:
@@ -364,10 +364,10 @@ def test_animated_gif():
         ims.append(im)
     
     # Store - animated GIF always poops out RGB
-    for float in (False, True):
+    for isfloat in (False, True):
         for colors in (3, 4):
             ims1 = ims[:]
-            if float:
+            if isfloat:
                 ims1 = [x.astype(np.float32) / 256 for x in ims1]
             ims1 = [x[:, :, :colors] for x in ims1]
             fname = fnamebase + '.animated.%i.gif' % colors
@@ -420,15 +420,15 @@ def test_ico():
     if os.getenv('TRAVIS', '') == 'true' and sys.version_info >= (3, 4):
         skip('Freeimage ico is unstable for this Travis build')
     
-    for float in (False, True):
+    for isfloat 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)
+                fname = fnamebase + '%i.%i.%i.ico' % (isfloat, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat)
                 rim = rim[:32, :32]  # ico needs nice size
                 imageio.imsave(fname, rim)
                 im = imageio.imread(fname)
-                mul = 255 if float else 1
+                mul = 255 if isfloat else 1
                 assert_close(rim * mul, im, 0.1)  # lossless
     
     # Meta data
diff --git a/tests/test_swf.py b/tests/test_swf.py
index 9cabec2..22b2622 100644
--- a/tests/test_swf.py
+++ b/tests/test_swf.py
@@ -168,9 +168,12 @@ 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
+    for dtype in [np.uint8, np.uint16, np.uint32, np.uint64,
+                  np.int8, np.int16, np.int32, np.int64,
+                  np.float16, np.float32, np.float64]:
+        for shape in [(100, 1), (100, 3)]:
+            # Repeats an identity matrix, just for testing
+            im1 = np.dstack((np.identity(shape[0], dtype=dtype), )*shape[1])
             imageio.mimsave(fname2, [im1], 'swf')
             im2 = imageio.mimread(fname2, 'swf')[0]
             assert im2.shape == (100, 100, 4)