Rewrite convert function

This commit is contained in:
cgohlke
2012-05-22 00:21:32 -07:00
parent 8f43ee7739
commit 3ff1ebf526
+114 -70
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].
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):