From dff2ee74b00173f1be259850e8768df3442be3e7 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 26 Sep 2011 16:34:00 -0700 Subject: [PATCH] ENH: Add dtype converters. --- scikits/image/__init__.py | 2 + scikits/image/util/dtype.py | 148 +++++++++++++++++++++++++ scikits/image/util/tests/test_dtype.py | 37 +++++++ 3 files changed, 187 insertions(+) create mode 100644 scikits/image/util/dtype.py create mode 100644 scikits/image/util/tests/test_dtype.py diff --git a/scikits/image/__init__.py b/scikits/image/__init__.py index 35108d27..a625165b 100644 --- a/scikits/image/__init__.py +++ b/scikits/image/__init__.py @@ -45,3 +45,5 @@ def get_log(name): import logging, sys logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) return logging.getLogger(name) + +from util.dtype import * diff --git a/scikits/image/util/dtype.py b/scikits/image/util/dtype.py new file mode 100644 index 00000000..1abbbe4e --- /dev/null +++ b/scikits/image/util/dtype.py @@ -0,0 +1,148 @@ +from __future__ import division +import numpy as np + +__all__ = ['img_as_float', 'img_as_int', 'img_as_uint'] + +from .. import get_log +log = get_log('dtype_converter') + +dtype_range = {np.uint8: (0, 255), + np.uint16: (0, 65535), + np.int8: (-128, 127), + np.int16: (-32768, 32767), + np.float32: (0, 1), + np.float64: (0, 1)} + +integer_types = (np.uint8, np.int16, np.int8, np.int16) + +def _convert(image, dtype, prec_loss): + """ + Convert an image to the requested data-type. + + Warnings are issues in case of precision loss, or when + negative values have to be scaled into the positive domain. + + Parameters + ---------- + image : ndarray + Input image. + dtype : dtype + Target data-type. + prec_loss : tuple + List of input data-types that, when converted to `dtype`, + would lose precision. + + """ + image = np.asarray(image) + dtype_in = image.dtype.type + + if dtype_in == dtype: + return image + + if dtype_in in prec_loss: + log.warn('Possible precision loss, converting from ' + '%s to %s' % (np.dtype(dtype_in), np.dtype(dtype))) + + try: + imin, imax = dtype_range[dtype_in] + omin, omax = dtype_range[dtype] + except KeyError: + raise ValueError("Unsure how to convert %s to %s." % \ + (np.dtype(dtype_in), np.dtype(dtype))) + + sign_loss = (np.sign(imin) == -1) and (np.sign(omin) != -1) + + if sign_loss: + log.warn('Possible sign loss when converting ' + 'negative image of type %s to positive ' + 'image of type %s.' % (np.dtype(dtype_in), np.dtype(dtype))) + + # If input type is non-negative, or if + # converting to a positive-only type, then we + # there's no need to shift numbers to the negative side + if sign_loss or np.sign(imin) != -1: + shift = 0 + omin = 0 + else: + shift = omin + + scale = (omax - omin) / (imax - imin) + + if dtype in integer_types: + round_fn = np.round + else: + round_fn = lambda x: x + + # Do scaling/shifting calculations in floating point + image = image.astype(np.float64) + out = np.empty_like(image, dtype=dtype) + + out[:] = round_fn((image - imin) * scale + shift) + + return out + +def img_as_float(image): + """Convert an image to double-precision floating point format. + + Parameters + ---------- + image : ndarray + Input image. + + Returns + ------- + out : ndarray of float64 + Output image. + + Notes + ----- + The range of a floating point image is [0, 1]. + Negative input values will be shifted to the positive domain. + + """ + prec_loss = () + return _convert(image, np.float64, prec_loss) + +def img_as_uint(image): + """Convert an image to 64-bit unsigned integer format. + + Parameters + ---------- + image : ndarray + Input image. + + Returns + ------- + out : ndarray of uint64 + Output image. + + Notes + ----- + Negative input values will be shifted to the positive domain. + + """ + + prec_loss = (np.float32, np.float64) + return _convert(image, np.uint16, prec_loss) + +def img_as_int(image): + """Convert an image to 64-bit signed integer format. + + Parameters + ---------- + image : ndarray + Input image. + + Returns + ------- + out : ndarray of uint64 + Output image. + + Notes + ----- + If the input data-type is positive-only (e.g., uint8), then + the output image will still only have positive values. + + """ + prec_loss = (np.float32, np.float64, np.uint16) + return _convert(image, np.int16, prec_loss) diff --git a/scikits/image/util/tests/test_dtype.py b/scikits/image/util/tests/test_dtype.py new file mode 100644 index 00000000..a3ef2377 --- /dev/null +++ b/scikits/image/util/tests/test_dtype.py @@ -0,0 +1,37 @@ +import numpy as np +from numpy.testing import assert_equal +from scikits.image import img_as_int, img_as_float, img_as_uint + +dtype_range = {np.uint8: (0, 255), + np.uint16: (0, 65535), + np.int8: (-128, 127), + np.int16: (-32768, 32767), + np.float32: (0, 1), + np.float64: (0, 1)} + +def _verify_range(msg, x, vmin, vmax): + assert_equal(x[0], vmin) + assert_equal(x[-1], vmax) + +def test_range(): + for dtype in dtype_range: + imin, imax = dtype_range[dtype] + x = np.linspace(imin, imax, 10).astype(dtype) + + for (f, dt) in [(img_as_int, np.int16), + (img_as_float, np.float64), + (img_as_uint, np.uint16)]: + y = f(x) + + omin, omax = dtype_range[dt] + + if imin == 0: + omin = 0 + + yield _verify_range, \ + "From %s to %s" % (np.dtype(dtype), np.dtype(dt)), \ + y, omin, omax + +if __name__ == '__main__': + np.testing.run_module_suite() +