From 3ff1ebf52656c5ea3fad67875b85e0359ffccd9f Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 00:21:32 -0700 Subject: [PATCH 01/22] Rewrite convert function --- skimage/util/dtype.py | 184 ++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 70 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index e9bc3036..7d4c0900 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -10,8 +10,8 @@ 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)} + np.float32: (-1, 1), + np.float64: (-1, 1)} integer_types = (np.uint8, np.uint16, np.int8, np.int16) @@ -20,19 +20,24 @@ _supported_types = (np.uint8, np.uint16, np.uint32, np.float32, np.float64) if np.__version__ >= "1.6.0": - dtype_range[np.float16] = (0, 1) + dtype_range[np.float16] = (-1, 1) _supported_types += (np.float16, ) -def convert(image, dtype, force_copy=False): +def convert(image, dtype, force_copy=False, uniform=False): """ Convert an image to the requested data-type. Warnings are issued in case of precision loss, or when negative values have to be scaled into the positive domain. - Floating point values must be in the range [0.0, 1.0]. + + Floating point values are expected to be normalized. They will be + clipped to the range [0.0, 1.0] or [-1.0, 1.0] when converting to + unsigned respectively signed integers. + Numbers are not shifted to the negative side when converting from - floating point or unsigned integer types to signed integer types. + unsigned to signed integer types. Negative values will be clipped from + signed integers when converting to unsigned integers. Parameters ---------- @@ -42,6 +47,9 @@ def convert(image, dtype, force_copy=False): Target data-type. force_copy : bool Force a copy of the data, irrespective of its current dtype. + uniform : bool + Quantize the floating point range to integer range uniformly instead + of scaling and rounding floating point values to the nearest integers. """ image = np.asarray(image) @@ -74,93 +82,129 @@ def convert(image, dtype, force_copy=False): # Return first of `dtypes` with itemsize greater than `itemsize` return next(dt for dt in dtypes if itemsize < np.dtype(dt).itemsize) + def _dtype2(kind, bits, itemsize=1): + # Return dtype of `kind` that can store a `bits` wide unsigned int + c = lambda x, y: x <= y if kind == 'u' else x < y + s = next(i for i in (itemsize, ) + (2, 4, 8) if c(bits, i*8)) + return np.dtype(kind + str(s)) + + def _scale(a, n, m, copy=True): + # Scale unsigned integers from n to m bits + # Numbers can be represented exactly only if m is a multiple of n + # Output array is of same kind as input. + kind = a.dtype.kind + if n == m: + return a.copy() if copy else a + elif n > m: + # downscale with precision loss + prec_loss() + if copy: + b = np.empty(a.shape, _dtype2(kind, m)) + np.divide(a, 2**(n - m), out=b) + return b + else: + a //= 2**(n - m) + return a + elif m % n == 0: + # exact upscale to a multiple of n bits + if copy: + b = np.empty(a.shape, _dtype2(kind, m)) + np.multiply(a, (2**m - 1) / (2**n - 1), out=b) + return b + else: + a = np.array(a, _dtype2(kind, m, a.dtype.itemsize), copy=False) + a *= (2**m - 1) / (2**n - 1) + return a + else: + # upscale to a multiple of n bits, + # then downscale with precision loss + prec_loss() + o = (m // n + 1) * n + if copy: + b = np.empty(a.shape, _dtype2(kind, o)) + np.multiply(a, (2**o - 1) / (2**n - 1), out=b) + b //= 2**(o - m) + return b + else: + a = np.array(a, _dtype2(kind, o, a.dtype.itemsize), copy=False) + a *= (2**o - 1) / (2**n - 1) + a //= 2**(o - m) + return a + if kind_in == 'f': - if np.min(image) < 0 or np.max(image) > 1: - raise ValueError("Images of type float must be between 0 and 1") if kind == 'f': # floating point -> floating point if itemsize_in > itemsize: prec_loss() return dtype(image) + # floating point -> integer prec_loss() # use float type that can represent output integer type image = np.array(image, _dtype(itemsize, dtype_in, np.float32, np.float64)) - image *= np.iinfo(dtype).max + 1 - np.clip(image, 0, np.iinfo(dtype).max, out=image) + if not uniform: + if kind == 'u': + image *= np.iinfo(dtype).max + else: + image *= np.iinfo(dtype).max - np.iinfo(dtype).min + image -= 1.0 + image /= 2.0 + np.rint(image, out=image) + np.clip(image, np.iinfo(dtype).min, np.iinfo(dtype).max, out=image) + elif kind == 'u': + image *= np.iinfo(dtype).max + 1 + np.clip(image, 0, np.iinfo(dtype).max, out=image) + else: + image += 1.0 + image *= (np.iinfo(dtype).max - np.iinfo(dtype).min + 1.0) / 2.0 + np.clip(image, np.iinfo(dtype).min, np.iinfo(dtype).max, out=image) + image -= np.iinfo(dtype).min return dtype(image) + if kind == 'f': # integer -> floating point if itemsize_in >= itemsize: prec_loss() - # use float type that can represent input integers + # use float type that can exactly represent input integers image = np.array(image, _dtype(itemsize_in, dtype, np.float32, np.float64)) - if np.iinfo(dtype_in).min: - sign_loss() - image -= np.iinfo(dtype_in).min - image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min - return dtype(image) - if kind_in == 'u': - # unsigned integer -> integer - shift = 1 if kind == 'i' else 0 - if itemsize_in > itemsize: - prec_loss() - image = image >> 8 * (itemsize_in - itemsize) + shift - return dtype(image) - result = dtype(image) - result <<= 8 * (itemsize - itemsize_in) - shift - if itemsize - itemsize_in == 3: - # uint8 -> (u)int32 - # hint: 4294967295 == (255 << 24) + (255 << 16) + (255 << 8) + 255 - image = dtype(image) - image *= 2**16 + 2**8 + 1 - if shift: - result += image >> shift + if kind_in == 'u': + image /= np.iinfo(dtype_in).max + # DirectX uses this conversion also for signed ints + #if np.iinfo(dtype_in).min: + # np.maximum(image, -1.0, out=image) else: - result += image - return dtype(result) + image *= 2.0 + image += 1.0 + image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min + return dtype(image) + + if kind_in == 'u': + if kind == 'i': + # unsigned integer -> signed integer + image = _scale(image, 8*itemsize_in, 8*itemsize-1) + return image.view(dtype) + else: + # unsigned integer -> unsigned integer + return _scale(image, 8*itemsize_in, 8*itemsize) + if kind == 'u': # signed integer -> unsigned integer sign_loss() - # use next higher precision signed integer type - image = np.array(image, _dtype(itemsize_in, - np.int16, np.int32, np.int64)) - image -= np.iinfo(dtype_in).min - if itemsize_in == itemsize: - return dtype(image) - if itemsize_in > itemsize: - prec_loss() - image >>= 8 * (itemsize_in - itemsize) - return dtype(image) - result = dtype(image) - result <<= 8 * (itemsize - itemsize_in) - if itemsize - itemsize_in == 3: - # int8 -> uint32 - image = dtype(image) - image *= 2**16 + 2**8 + 1 - result += dtype(image) + image = _scale(image, 8*itemsize_in-1, 8*itemsize) + result = np.empty(image.shape, dtype) + np.maximum(image, 0, out=result) return result - if kind == 'i': - # signed integer -> signed integer - if itemsize_in > itemsize: - prec_loss() - return dtype(image // 2**(8 * (itemsize_in - itemsize))) - # use next higher precision signed integer type - image = np.array(image, _dtype(itemsize_in, - np.int16, np.int32, np.int64)) - image -= np.iinfo(dtype_in).min - # use next higher precision signed integer type - result = np.array(image, _dtype(image.itemsize, np.int32, np.int64)) - result *= 2**(8 * (itemsize - itemsize_in)) - if itemsize - itemsize_in == 3: - # int8 -> int32 - image = dtype(image) - image *= 2**16 + 2**8 + 1 - result += image - result += np.iinfo(dtype).min - return dtype(result) + + # signed integer -> signed integer + if itemsize_in > itemsize: + return _scale(image, 8*itemsize_in-1, 8*itemsize-1) + image = image.astype(_dtype2('i', itemsize*8)) + image -= np.iinfo(dtype_in).min + image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False) + image += np.iinfo(dtype).min + return image def img_as_float(image, force_copy=False): From e4b49239c4151a4ed0d190eb0a57f6f555285ef6 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 00:25:42 -0700 Subject: [PATCH 02/22] Adjust tests for new type conversion rules --- skimage/util/tests/test_dtype.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/skimage/util/tests/test_dtype.py b/skimage/util/tests/test_dtype.py index d5067981..e42030b0 100644 --- a/skimage/util/tests/test_dtype.py +++ b/skimage/util/tests/test_dtype.py @@ -9,8 +9,8 @@ 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)} + np.float32: (-1.0, 1.0), + np.float64: (-1.0, 1.0)} def _verify_range(msg, x, vmin, vmax): assert_equal(x[0], vmin) @@ -29,8 +29,9 @@ def test_range(): omin, omax = dtype_range[dt] - if imin == 0: + if imin == 0 or omin == 0: omin = 0 + imin = 0 yield _verify_range, \ "From %s to %s" % (np.dtype(dtype), np.dtype(dt)), \ @@ -67,13 +68,6 @@ def test_unsupported_dtype(): assert_raises(ValueError, img_as_int, x) -def test_float_out_of_range(): - too_high = np.array([2], dtype=np.float32) - assert_raises(ValueError, img_as_int, too_high) - too_low = np.array([-1], dtype=np.float32) - assert_raises(ValueError, img_as_int, too_low) - - def test_copy(): x = np.array([1], dtype=np.float64) y = img_as_float(x) @@ -84,4 +78,3 @@ def test_copy(): if __name__ == '__main__': np.testing.run_module_suite() - From fc42d86894322f1e0a43585d9910e05bd3b3d5bc Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 00:28:32 -0700 Subject: [PATCH 03/22] Adjust rescale_intensity function to new dtype conversion rules --- skimage/exposure/exposure.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 2d3b82f5..8d047837 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -177,6 +177,9 @@ def rescale_intensity(image, in_range=None, out_range=None): else: omin, omax = out_range + if imin >= 0: + omin = 0 + image = np.clip(image, imin, imax) image = (image - imin) / float(imax - imin) From 64da74c91a630e7b88407ce63e3cd1cd3c8dfbb3 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 11:12:47 -0700 Subject: [PATCH 04/22] Fix grammar --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 7d4c0900..20c1d4cc 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -33,7 +33,7 @@ def convert(image, dtype, force_copy=False, uniform=False): Floating point values are expected to be normalized. They will be clipped to the range [0.0, 1.0] or [-1.0, 1.0] when converting to - unsigned respectively signed integers. + unsigned or signed integers respectively. Numbers are not shifted to the negative side when converting from unsigned to signed integer types. Negative values will be clipped from From 754f0e5d39301cab66259b897d53a55c2f0e7f09 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 11:39:38 -0700 Subject: [PATCH 05/22] Docstring additions --- skimage/util/dtype.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 20c1d4cc..157d2684 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -48,8 +48,21 @@ def convert(image, dtype, force_copy=False, uniform=False): force_copy : bool Force a copy of the data, irrespective of its current dtype. uniform : bool - Quantize the floating point range to integer range uniformly instead - of scaling and rounding floating point values to the nearest integers. + Uniformly quantize the floating point range to the integer range. + By default (uniform=False) floating point values are scaled and + rounded to the nearest integers, which minimized back and forth + conversion errors. + + References + ---------- + (1) DirectX data conversion rules. + http://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx + (2) Data Conversions. + In "OpenGL ES 2.0 Specification v2.0.25", pp 7-8. Khronos Group, 2010. + (3) Proper treatment of pixels as integers. A.W. Paeth. + In "Graphics Gems I", pp 249-256. Morgan Kaufmann, 1990. + (4) Dirty Pixels. J. Blinn. + In "Jim Blinn's corner: Dirty Pixels", pp 47-57. Morgan Kaufmann, 1998. """ image = np.asarray(image) From 8e67dd901f95288442f14812d2a12283f10e8200 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 11:49:17 -0700 Subject: [PATCH 06/22] Fix spelling --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 157d2684..372eb683 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -50,7 +50,7 @@ def convert(image, dtype, force_copy=False, uniform=False): uniform : bool Uniformly quantize the floating point range to the integer range. By default (uniform=False) floating point values are scaled and - rounded to the nearest integers, which minimized back and forth + rounded to the nearest integers, which minimizes back and forth conversion errors. References From 85670d60b6730266d1998442c0b24c131f19ea4f Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 12:13:51 -0700 Subject: [PATCH 07/22] Update docstring --- skimage/util/dtype.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 372eb683..93e9035a 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -28,16 +28,16 @@ def convert(image, dtype, force_copy=False, uniform=False): """ Convert an image to the requested data-type. - Warnings are issued in case of precision loss, or when - negative values have to be scaled into the positive domain. + Warnings are issued in case of precision loss, or when negative values + are clipped during conversion to unsigned integer types (sign loss). Floating point values are expected to be normalized. They will be clipped to the range [0.0, 1.0] or [-1.0, 1.0] when converting to unsigned or signed integers respectively. Numbers are not shifted to the negative side when converting from - unsigned to signed integer types. Negative values will be clipped from - signed integers when converting to unsigned integers. + unsigned to signed integer types. Negative values will be clipped when + converting to unsigned integers. Parameters ---------- From 7e51e519b1759908e5576ab451304fd646ad3f4b Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 12:21:58 -0700 Subject: [PATCH 08/22] Fix whitespace --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 93e9035a..183a9cd2 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -195,7 +195,7 @@ def convert(image, dtype, force_copy=False, uniform=False): if kind_in == 'u': if kind == 'i': - # unsigned integer -> signed integer + # unsigned integer -> signed integer image = _scale(image, 8*itemsize_in, 8*itemsize-1) return image.view(dtype) else: From 31d341c0cb37bab7a4b4165d1389a68404ff0acd Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 18:23:35 -0700 Subject: [PATCH 09/22] Fix uniform conversion to signed int --- skimage/util/dtype.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 183a9cd2..ae1f80b7 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -169,10 +169,9 @@ def convert(image, dtype, force_copy=False, uniform=False): image *= np.iinfo(dtype).max + 1 np.clip(image, 0, np.iinfo(dtype).max, out=image) else: - image += 1.0 image *= (np.iinfo(dtype).max - np.iinfo(dtype).min + 1.0) / 2.0 + np.floor(image, out=image) np.clip(image, np.iinfo(dtype).min, np.iinfo(dtype).max, out=image) - image -= np.iinfo(dtype).min return dtype(image) if kind == 'f': From b7045e6cd0e7e52f5a691efb6f30ea948b1c8ace Mon Sep 17 00:00:00 2001 From: cgohlke Date: Tue, 22 May 2012 18:58:22 -0700 Subject: [PATCH 10/22] Fix rescale_intensity --- skimage/exposure/exposure.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 8d047837..a0a576d6 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -174,12 +174,11 @@ def rescale_intensity(image, in_range=None, out_range=None): if out_range is None: omin, omax = dtype_range[dtype] + if imin >= 0: + omin = 0 else: omin, omax = out_range - if imin >= 0: - omin = 0 - image = np.clip(image, imin, imax) image = (image - imin) / float(imax - imin) From 5153c97b212202dfa65941a84ccd4b03e70dda59 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 00:16:00 -0700 Subject: [PATCH 11/22] Fix numpy 1.7dev casting --- skimage/util/dtype.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index ae1f80b7..82f48342 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -113,7 +113,8 @@ def convert(image, dtype, force_copy=False, uniform=False): prec_loss() if copy: b = np.empty(a.shape, _dtype2(kind, m)) - np.divide(a, 2**(n - m), out=b) + np.divide(a, 2**(n - m), out=b, dtype=a.dtype, + casting='unsafe') return b else: a //= 2**(n - m) @@ -122,11 +123,11 @@ def convert(image, dtype, force_copy=False, uniform=False): # exact upscale to a multiple of n bits if copy: b = np.empty(a.shape, _dtype2(kind, m)) - np.multiply(a, (2**m - 1) / (2**n - 1), out=b) + np.multiply(a, (2**m - 1) // (2**n - 1), out=b, dtype=b.dtype) return b else: a = np.array(a, _dtype2(kind, m, a.dtype.itemsize), copy=False) - a *= (2**m - 1) / (2**n - 1) + a *= (2**m - 1) // (2**n - 1) return a else: # upscale to a multiple of n bits, @@ -135,12 +136,12 @@ def convert(image, dtype, force_copy=False, uniform=False): o = (m // n + 1) * n if copy: b = np.empty(a.shape, _dtype2(kind, o)) - np.multiply(a, (2**o - 1) / (2**n - 1), out=b) + np.multiply(a, (2**o - 1) // (2**n - 1), out=b, dtype=b.dtype) b //= 2**(o - m) return b else: a = np.array(a, _dtype2(kind, o, a.dtype.itemsize), copy=False) - a *= (2**o - 1) / (2**n - 1) + a *= (2**o - 1) // (2**n - 1) a //= 2**(o - m) return a @@ -206,7 +207,7 @@ def convert(image, dtype, force_copy=False, uniform=False): sign_loss() image = _scale(image, 8*itemsize_in-1, 8*itemsize) result = np.empty(image.shape, dtype) - np.maximum(image, 0, out=result) + np.maximum(image, 0, out=result, dtype=image.dtype, casting='unsafe') return result # signed integer -> signed integer From 8575a46f4201c32fe6e5a58e412e3c5dedf1e752 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 00:55:28 -0700 Subject: [PATCH 12/22] Verify dtype --- skimage/util/tests/test_dtype.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/skimage/util/tests/test_dtype.py b/skimage/util/tests/test_dtype.py index e42030b0..9597c784 100644 --- a/skimage/util/tests/test_dtype.py +++ b/skimage/util/tests/test_dtype.py @@ -12,9 +12,10 @@ dtype_range = {np.uint8: (0, 255), np.float32: (-1.0, 1.0), np.float64: (-1.0, 1.0)} -def _verify_range(msg, x, vmin, vmax): +def _verify_range(msg, x, vmin, vmax, dtype): assert_equal(x[0], vmin) assert_equal(x[-1], vmax) + assert x.dtype == dtype def test_range(): for dtype in dtype_range: @@ -33,9 +34,9 @@ def test_range(): omin = 0 imin = 0 - yield _verify_range, \ - "From %s to %s" % (np.dtype(dtype), np.dtype(dt)), \ - y, omin, omax + yield (_verify_range, + "From %s to %s" % (np.dtype(dtype), np.dtype(dt)), + y, omin, omax, np.dtype(dt)) def test_range_extra_dtypes(): @@ -58,9 +59,9 @@ def test_range_extra_dtypes(): x = np.linspace(imin, imax, 10).astype(dtype_in) y = convert(x, dt) omin, omax = dtype_range_extra[dt] - yield _verify_range, \ - "From %s to %s" % (np.dtype(dtype_in), np.dtype(dt)), \ - y, omin, omax + yield (_verify_range, + "From %s to %s" % (np.dtype(dtype_in), np.dtype(dt)), + y, omin, omax, np.dtype(dt)) def test_unsupported_dtype(): From c4552d294acf80cd509699f9c9e4e19e64303492 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 00:57:26 -0700 Subject: [PATCH 13/22] Return correct dtype --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 82f48342..c2d041f0 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -217,7 +217,7 @@ def convert(image, dtype, force_copy=False, uniform=False): image -= np.iinfo(dtype_in).min image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False) image += np.iinfo(dtype).min - return image + return dtype(image) def img_as_float(image, force_copy=False): From 4377c647f4c786ab1b1f7a80df419f48725bc215 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 01:35:36 -0700 Subject: [PATCH 14/22] Code cleanup --- skimage/util/dtype.py | 45 +++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index c2d041f0..8f35a545 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -66,14 +66,10 @@ def convert(image, dtype, force_copy=False, uniform=False): """ image = np.asarray(image) - dtype = np.dtype(dtype).type - dtype_in = image.dtype.type dtypeobj = np.dtype(dtype) - dtypeobj_in = np.dtype(dtype_in) - kind = dtypeobj.kind - kind_in = dtypeobj_in.kind - itemsize = dtypeobj.itemsize - itemsize_in = dtypeobj_in.itemsize + dtypeobj_in = image.dtype + dtype = dtypeobj.type + dtype_in = dtypeobj_in.type if dtype_in == dtype: if force_copy: @@ -145,6 +141,17 @@ def convert(image, dtype, force_copy=False, uniform=False): a //= 2**(o - m) return a + kind = dtypeobj.kind + kind_in = dtypeobj_in.kind + itemsize = dtypeobj.itemsize + itemsize_in = dtypeobj_in.itemsize + if kind in 'ui': + imin = np.iinfo(dtype).min + imax = np.iinfo(dtype).max + if kind_in in 'ui': + imin_in = np.iinfo(dtype_in).min + imax_in = np.iinfo(dtype_in).max + if kind_in == 'f': if kind == 'f': # floating point -> floating point @@ -159,20 +166,20 @@ def convert(image, dtype, force_copy=False, uniform=False): np.float32, np.float64)) if not uniform: if kind == 'u': - image *= np.iinfo(dtype).max + image *= imax else: - image *= np.iinfo(dtype).max - np.iinfo(dtype).min + image *= imax - imin image -= 1.0 image /= 2.0 np.rint(image, out=image) - np.clip(image, np.iinfo(dtype).min, np.iinfo(dtype).max, out=image) + np.clip(image, imin, imax, out=image) elif kind == 'u': - image *= np.iinfo(dtype).max + 1 - np.clip(image, 0, np.iinfo(dtype).max, out=image) + image *= imax + 1 + np.clip(image, 0, imax, out=image) else: - image *= (np.iinfo(dtype).max - np.iinfo(dtype).min + 1.0) / 2.0 + image *= (imax - imin + 1.0) / 2.0 np.floor(image, out=image) - np.clip(image, np.iinfo(dtype).min, np.iinfo(dtype).max, out=image) + np.clip(image, imin, imax, out=image) return dtype(image) if kind == 'f': @@ -183,14 +190,14 @@ def convert(image, dtype, force_copy=False, uniform=False): image = np.array(image, _dtype(itemsize_in, dtype, np.float32, np.float64)) if kind_in == 'u': - image /= np.iinfo(dtype_in).max + image /= imax_in # DirectX uses this conversion also for signed ints - #if np.iinfo(dtype_in).min: + #if imin_in: # np.maximum(image, -1.0, out=image) else: image *= 2.0 image += 1.0 - image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min + image /= imax_in - imin_in return dtype(image) if kind_in == 'u': @@ -214,9 +221,9 @@ def convert(image, dtype, force_copy=False, uniform=False): if itemsize_in > itemsize: return _scale(image, 8*itemsize_in-1, 8*itemsize-1) image = image.astype(_dtype2('i', itemsize*8)) - image -= np.iinfo(dtype_in).min + image -= imin_in image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False) - image += np.iinfo(dtype).min + image += imin return dtype(image) From dad16f8a29d92ebeab2be5ccc503a9657826b2b3 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 11:33:19 -0700 Subject: [PATCH 15/22] Restore test_float_out_of_range --- skimage/util/tests/test_dtype.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/skimage/util/tests/test_dtype.py b/skimage/util/tests/test_dtype.py index 9597c784..2ef0044f 100644 --- a/skimage/util/tests/test_dtype.py +++ b/skimage/util/tests/test_dtype.py @@ -69,6 +69,13 @@ def test_unsupported_dtype(): assert_raises(ValueError, img_as_int, x) +def test_float_out_of_range(): + too_high = np.array([2], dtype=np.float32) + assert_raises(ValueError, img_as_int, too_high) + too_low = np.array([-2], dtype=np.float32) + assert_raises(ValueError, img_as_int, too_low) + + def test_copy(): x = np.array([1], dtype=np.float64) y = img_as_float(x) From 11c4ca7f53f5bc69f3afd24653d785a5881b8d74 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 11:37:07 -0700 Subject: [PATCH 16/22] Add floating point range check --- skimage/util/dtype.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 8f35a545..b76e95f8 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -24,7 +24,7 @@ if np.__version__ >= "1.6.0": _supported_types += (np.float16, ) -def convert(image, dtype, force_copy=False, uniform=False): +def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): """ Convert an image to the requested data-type. @@ -52,6 +52,13 @@ def convert(image, dtype, force_copy=False, uniform=False): By default (uniform=False) floating point values are scaled and rounded to the nearest integers, which minimizes back and forth conversion errors. + frange: [fmin, fmax] + Range of floating point values. An error is raised if any input + floating point values are smaller than fmin or larger than fmax. + The default is [-1.5, 1.5], which allows for some outliers but + catches the common case where normalized integer images are of + floating point type. No range check is performed if `frange` is empty + or evaluates to False. References ---------- @@ -153,6 +160,9 @@ def convert(image, dtype, force_copy=False, uniform=False): imax_in = np.iinfo(dtype_in).max if kind_in == 'f': + if frange and np.min(image) < frange[0] or np.max(image) > frange[1]: + raise ValueError("Images of type float must be between %d and %d", + frange) if kind == 'f': # floating point -> floating point if itemsize_in > itemsize: From 15c0df33c97b334fe122af5916a1694a198298ee Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 12:03:23 -0700 Subject: [PATCH 17/22] Fix TypeError --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index b76e95f8..3340434a 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -162,7 +162,7 @@ def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): if kind_in == 'f': if frange and np.min(image) < frange[0] or np.max(image) > frange[1]: raise ValueError("Images of type float must be between %d and %d", - frange) + tuple(frange)) if kind == 'f': # floating point -> floating point if itemsize_in > itemsize: From 225cfca4e2ea4cf27b09d37bdae685be4b5b12f2 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 23 May 2012 12:09:41 -0700 Subject: [PATCH 18/22] Use general format --- skimage/util/dtype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 3340434a..ab56b98e 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -161,7 +161,7 @@ def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): if kind_in == 'f': if frange and np.min(image) < frange[0] or np.max(image) > frange[1]: - raise ValueError("Images of type float must be between %d and %d", + raise ValueError("Images of type float must be between %g and %g", tuple(frange)) if kind == 'f': # floating point -> floating point From 6d997b9af70f3cbf53b2031987a92b858e591e12 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Thu, 24 May 2012 01:18:49 -0700 Subject: [PATCH 19/22] Remove controversial range check --- skimage/util/dtype.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index ab56b98e..ed1ae35b 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -24,16 +24,15 @@ if np.__version__ >= "1.6.0": _supported_types += (np.float16, ) -def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): +def convert(image, dtype, force_copy=False, uniform=False): """ Convert an image to the requested data-type. Warnings are issued in case of precision loss, or when negative values are clipped during conversion to unsigned integer types (sign loss). - Floating point values are expected to be normalized. They will be - clipped to the range [0.0, 1.0] or [-1.0, 1.0] when converting to - unsigned or signed integers respectively. + Floating point values will be clipped to the range [0.0, 1.0] or + [-1.0, 1.0] when converting to unsigned or signed integers respectively. Numbers are not shifted to the negative side when converting from unsigned to signed integer types. Negative values will be clipped when @@ -52,13 +51,6 @@ def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): By default (uniform=False) floating point values are scaled and rounded to the nearest integers, which minimizes back and forth conversion errors. - frange: [fmin, fmax] - Range of floating point values. An error is raised if any input - floating point values are smaller than fmin or larger than fmax. - The default is [-1.5, 1.5], which allows for some outliers but - catches the common case where normalized integer images are of - floating point type. No range check is performed if `frange` is empty - or evaluates to False. References ---------- @@ -105,7 +97,7 @@ def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): return np.dtype(kind + str(s)) def _scale(a, n, m, copy=True): - # Scale unsigned integers from n to m bits + # Scale unsigned/positive integers from n to m bits # Numbers can be represented exactly only if m is a multiple of n # Output array is of same kind as input. kind = a.dtype.kind @@ -160,9 +152,6 @@ def convert(image, dtype, force_copy=False, uniform=False, frange=[-1.5, 1.5]): imax_in = np.iinfo(dtype_in).max if kind_in == 'f': - if frange and np.min(image) < frange[0] or np.max(image) > frange[1]: - raise ValueError("Images of type float must be between %g and %g", - tuple(frange)) if kind == 'f': # floating point -> floating point if itemsize_in > itemsize: From bbc5b8e552f490deba8dcf8ee252ac1a61d79d64 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Thu, 24 May 2012 01:20:51 -0700 Subject: [PATCH 20/22] Remove range check test --- skimage/util/tests/test_dtype.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/skimage/util/tests/test_dtype.py b/skimage/util/tests/test_dtype.py index 2ef0044f..9597c784 100644 --- a/skimage/util/tests/test_dtype.py +++ b/skimage/util/tests/test_dtype.py @@ -69,13 +69,6 @@ def test_unsupported_dtype(): assert_raises(ValueError, img_as_int, x) -def test_float_out_of_range(): - too_high = np.array([2], dtype=np.float32) - assert_raises(ValueError, img_as_int, too_high) - too_low = np.array([-2], dtype=np.float32) - assert_raises(ValueError, img_as_int, too_low) - - def test_copy(): x = np.array([1], dtype=np.float64) y = img_as_float(x) From 96bad2e4cf2ba6734e3fa7b43c8c0d6739234b09 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 6 Jun 2012 18:51:47 -0700 Subject: [PATCH 21/22] Add range check against better judgment --- skimage/util/dtype.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index ed1ae35b..44476e44 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -31,8 +31,9 @@ def convert(image, dtype, force_copy=False, uniform=False): Warnings are issued in case of precision loss, or when negative values are clipped during conversion to unsigned integer types (sign loss). - Floating point values will be clipped to the range [0.0, 1.0] or - [-1.0, 1.0] when converting to unsigned or signed integers respectively. + Floating point values are expected to be normalized and will be clipped + to the range [0.0, 1.0] or [-1.0, 1.0] when converting to unsigned or + signed integers respectively. Numbers are not shifted to the negative side when converting from unsigned to signed integer types. Negative values will be clipped when @@ -152,6 +153,8 @@ def convert(image, dtype, force_copy=False, uniform=False): imax_in = np.iinfo(dtype_in).max if kind_in == 'f': + if np.min(image) < -1.0 or np.max(image) > 1.0: + raise ValueError("Images of type float must be between -1 and 1.") if kind == 'f': # floating point -> floating point if itemsize_in > itemsize: From bde28317091d688dcbed96428535b35925938bf1 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Wed, 6 Jun 2012 18:53:44 -0700 Subject: [PATCH 22/22] Add test_float_out_of_range --- skimage/util/tests/test_dtype.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/skimage/util/tests/test_dtype.py b/skimage/util/tests/test_dtype.py index 9597c784..2ef0044f 100644 --- a/skimage/util/tests/test_dtype.py +++ b/skimage/util/tests/test_dtype.py @@ -69,6 +69,13 @@ def test_unsupported_dtype(): assert_raises(ValueError, img_as_int, x) +def test_float_out_of_range(): + too_high = np.array([2], dtype=np.float32) + assert_raises(ValueError, img_as_int, too_high) + too_low = np.array([-2], dtype=np.float32) + assert_raises(ValueError, img_as_int, too_low) + + def test_copy(): x = np.array([1], dtype=np.float64) y = img_as_float(x)