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 1/4] 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() + From edfb8262bae6fa0fc6208b3a09925c05fa1df962 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 26 Sep 2011 16:56:09 -0700 Subject: [PATCH 2/4] DOC: Fix dtype converter docstring typos. --- scikits/image/util/dtype.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scikits/image/util/dtype.py b/scikits/image/util/dtype.py index 1abbbe4e..60f05bb8 100644 --- a/scikits/image/util/dtype.py +++ b/scikits/image/util/dtype.py @@ -104,7 +104,7 @@ def img_as_float(image): return _convert(image, np.float64, prec_loss) def img_as_uint(image): - """Convert an image to 64-bit unsigned integer format. + """Convert an image to 16-bit unsigned integer format. Parameters ---------- @@ -113,7 +113,7 @@ def img_as_uint(image): Returns ------- - out : ndarray of uint64 + out : ndarray of uint16 Output image. Notes @@ -126,7 +126,7 @@ def img_as_uint(image): return _convert(image, np.uint16, prec_loss) def img_as_int(image): - """Convert an image to 64-bit signed integer format. + """Convert an image to 16-bit signed integer format. Parameters ---------- @@ -135,7 +135,7 @@ def img_as_int(image): Returns ------- - out : ndarray of uint64 + out : ndarray of uint16 Output image. Notes From 5ec5c55b1a7061164558572bb52350a675c22c13 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 26 Sep 2011 17:12:14 -0700 Subject: [PATCH 3/4] BUG: Fix typo in dtype converter integer list. --- scikits/image/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scikits/image/util/dtype.py b/scikits/image/util/dtype.py index 60f05bb8..8a690689 100644 --- a/scikits/image/util/dtype.py +++ b/scikits/image/util/dtype.py @@ -13,7 +13,7 @@ dtype_range = {np.uint8: (0, 255), np.float32: (0, 1), np.float64: (0, 1)} -integer_types = (np.uint8, np.int16, np.int8, np.int16) +integer_types = (np.uint8, np.uint16, np.int8, np.int16) def _convert(image, dtype, prec_loss): """ From 0b7df2626a8bdec47013d40c5f91088b8b391313 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Tue, 27 Sep 2011 01:16:35 -0700 Subject: [PATCH 4/4] ENH: Add img_as_ubyte. --- scikits/image/util/__init__.py | 2 ++ scikits/image/util/dtype.py | 24 +++++++++++++++++++++++- scikits/image/util/tests/test_dtype.py | 6 ++++-- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 scikits/image/util/__init__.py diff --git a/scikits/image/util/__init__.py b/scikits/image/util/__init__.py new file mode 100644 index 00000000..6eb4da1c --- /dev/null +++ b/scikits/image/util/__init__.py @@ -0,0 +1,2 @@ +from dtype import * + diff --git a/scikits/image/util/dtype.py b/scikits/image/util/dtype.py index 8a690689..7e86892a 100644 --- a/scikits/image/util/dtype.py +++ b/scikits/image/util/dtype.py @@ -1,7 +1,7 @@ from __future__ import division import numpy as np -__all__ = ['img_as_float', 'img_as_int', 'img_as_uint'] +__all__ = ['img_as_float', 'img_as_int', 'img_as_uint', 'img_as_ubyte'] from .. import get_log log = get_log('dtype_converter') @@ -146,3 +146,25 @@ def img_as_int(image): """ prec_loss = (np.float32, np.float64, np.uint16) return _convert(image, np.int16, prec_loss) + +def img_as_ubyte(image): + """Convert an image to 8-bit unsigned integer format. + + Parameters + ---------- + image : ndarray + Input image. + + Returns + ------- + out : ndarray of ubyte (uint8) + Output image. + + Notes + ----- + If the input data-type is positive-only (e.g., uint16), then + the output image will still only have positive values. + + """ + prec_loss = (np.float32, np.float64, np.uint16, np.int16, np.int8) + return _convert(image, np.ubyte, prec_loss) diff --git a/scikits/image/util/tests/test_dtype.py b/scikits/image/util/tests/test_dtype.py index a3ef2377..d7cc3259 100644 --- a/scikits/image/util/tests/test_dtype.py +++ b/scikits/image/util/tests/test_dtype.py @@ -1,6 +1,7 @@ import numpy as np from numpy.testing import assert_equal -from scikits.image import img_as_int, img_as_float, img_as_uint +from scikits.image import img_as_int, img_as_float, \ + img_as_uint, img_as_ubyte dtype_range = {np.uint8: (0, 255), np.uint16: (0, 65535), @@ -20,7 +21,8 @@ def test_range(): for (f, dt) in [(img_as_int, np.int16), (img_as_float, np.float64), - (img_as_uint, np.uint16)]: + (img_as_uint, np.uint16), + (img_as_ubyte, np.ubyte)]: y = f(x) omin, omax = dtype_range[dt]