Merge pull request #193 from cgohlke/patch-1

ENH: Fix dtype.convert function.
This commit is contained in:
Stefan van der Walt
2012-06-10 19:26:24 -07:00
3 changed files with 160 additions and 91 deletions
+2
View File
@@ -174,6 +174,8 @@ 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
+145 -79
View File
@@ -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].
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 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
floating point or unsigned integer types to signed integer types.
unsigned to signed integer types. Negative values will be clipped when
converting to unsigned integers.
Parameters
----------
@@ -42,17 +47,29 @@ 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
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 minimizes 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)
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:
@@ -74,93 +91,142 @@ 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/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
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, dtype=a.dtype,
casting='unsafe')
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, 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)
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, 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 - 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 np.min(image) < 0 or np.max(image) > 1:
raise ValueError("Images of type float must be between 0 and 1")
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:
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 *= imax
else:
image *= imax - imin
image -= 1.0
image /= 2.0
np.rint(image, out=image)
np.clip(image, imin, imax, out=image)
elif kind == 'u':
image *= imax + 1
np.clip(image, 0, imax, out=image)
else:
image *= (imax - imin + 1.0) / 2.0
np.floor(image, out=image)
np.clip(image, imin, imax, out=image)
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 /= imax_in
# DirectX uses this conversion also for signed ints
#if imin_in:
# np.maximum(image, -1.0, out=image)
else:
result += image
return dtype(result)
image *= 2.0
image += 1.0
image /= imax_in - imin_in
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, dtype=image.dtype, casting='unsafe')
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 -= imin_in
image = _scale(image, 8*itemsize_in, 8*itemsize, copy=False)
image += imin
return dtype(image)
def img_as_float(image, force_copy=False):
+13 -12
View File
@@ -9,12 +9,13 @@ 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):
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:
@@ -29,12 +30,13 @@ 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)), \
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():
@@ -57,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():
@@ -70,7 +72,7 @@ def test_unsupported_dtype():
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)
too_low = np.array([-2], dtype=np.float32)
assert_raises(ValueError, img_as_int, too_low)
@@ -84,4 +86,3 @@ def test_copy():
if __name__ == '__main__':
np.testing.run_module_suite()