From 038db9d1abb684e22656af4a954c94109ac0f94f Mon Sep 17 00:00:00 2001 From: cgohlke Date: Sun, 11 Dec 2011 12:01:33 -0800 Subject: [PATCH 1/2] Rewrite dtype._convert function --- skimage/util/dtype.py | 202 +++++++++++++++++++++++++++++++----------- 1 file changed, 149 insertions(+), 53 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 7a03fd5a..f573dda9 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -15,13 +15,20 @@ dtype_range = {np.uint8: (0, 255), integer_types = (np.uint8, np.uint16, np.int8, np.int16) +_supported_types = (np.uint8, np.uint16, np.uint32, + np.int8, np.int16, np.int32, + np.float16, np.float32, np.float64) -def _convert(image, dtype, prec_loss): + +def _convert(image, dtype): """ Convert an image to the requested data-type. - Warnings are issues in case of precision loss, or when + 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]. + Numbers are not shifted to the negative side when converting from + floating point or unsigned integer types to signed integer types. Parameters ---------- @@ -29,59 +36,153 @@ def _convert(image, dtype, prec_loss): 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 = 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 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))) + if not (dtype_in in _supported_types and dtype in _supported_types): + raise ValueError("can not convert %s to %s." % (dtypeobj_in, dtypeobj)) - 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))) + def sign_loss(): + log.warn("Possible sign loss when converting negative image of type " + "%s to positive image of type %s." % (dtypeobj_in, dtypeobj)) - sign_loss = (np.sign(imin) == -1) and (np.sign(omin) != -1) + def prec_loss(): + log.warn("Possible precision loss when converting from " + "%s to %s" % (dtypeobj_in, dtypeobj)) - 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 = image - imin - out *= scale - out += shift - out = round_fn(out).astype(dtype) - - return out + if kind_in == 'f': + if kind == 'f': + # floating point -> floating point + if itemsize_in > itemsize: + prec_loss() + return dtype(image) + if kind == 'u': + # floating point -> unsigned integer + prec_loss() + tdt = next(dt for dt in (dtype_in, np.float32, np.float64) + if itemsize * 8 < np.finfo(dt).nmant) + image = tdt(image) + umax = np.iinfo(dtype).max + image = image * (umax + 1) + np.clip(image, 0, umax, out=image) + return dtype(image) + if kind == 'i': + # floating point -> signed integer + prec_loss() + tdt = next(dt for dt in (dtype_in, np.float32, np.float64) + if itemsize * 8 < np.finfo(dt).nmant) + image = tdt(image) + image = image * (np.iinfo(dtype).max + 1) + np.clip(image, 0, np.iinfo(dtype).max, out=image) + return dtype(image) + if kind_in == 'u': + if kind == 'f': + # unsigned integer -> floating point + if itemsize_in * 8 > np.finfo(dtype).nmant: + prec_loss() + tdt = next(dt for dt in (dtype, np.float32, np.float64) + if itemsize_in * 8 < np.finfo(dt).nmant) + image = tdt(image) + image /= np.iinfo(dtype_in).max + return dtype(image) + if kind == 'u': + # unsigned integer -> unsigned integer + if itemsize_in > itemsize: + prec_loss() + image = image >> 8 * (itemsize_in - itemsize) + return dtype(image) + else: + result = dtype(image) + result <<= 8 * (itemsize - itemsize_in) + if itemsize - itemsize_in == 3: + # uint8 -> uint32 + image = dtype(image) + image *= 2**16 + 2**8 + 1 + result += image + return result + if kind == 'i': + # unsigned integer -> signed integer + if itemsize_in >= itemsize: + prec_loss() + image = image >> (8 * (itemsize_in - itemsize) + 1) + return dtype(image) + else: + result = dtype(image) + result <<= 8 * (itemsize - itemsize_in) - 1 + if itemsize - itemsize_in == 3: + # uint8 -> int32 + image = dtype(image) + image *= 2**16 + 2**8 + 1 + result += image >> 1 + return dtype(result) + if kind_in == 'i': + if kind == 'f': + # signed integer -> floating point + if itemsize_in * 8 > np.finfo(dtype).nmant: + prec_loss() + sign_loss() + tdt = next(dt for dt in (dtype, np.float32, np.float64) + if itemsize_in * 8 < np.finfo(dt).nmant) + image = tdt(image) + image -= np.iinfo(dtype_in).min + image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min + return dtype(image) + if kind == 'u': + # signed integer -> unsigned integer + sign_loss() + tdt = next(dt for dt in (np.int16, np.int32, np.int64) + if itemsize_in < np.dtype(dt).itemsize) + image = tdt(image) + 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) + else: + 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 += image + return result + if kind == 'i': + # signed integer -> signed integer + if itemsize_in > itemsize: + prec_loss() + image = image // 2**(8 * (itemsize_in - itemsize)) + return dtype(image) + else: + tdt = next(dt for dt in (dtype, np.int16, np.int32, np.int64) + if itemsize_in < np.dtype(dt).itemsize) + image = tdt(image) + image -= np.iinfo(dtype_in).min + tdt = next(dt for dt in (np.int32, np.int64) + if image.dtype.itemsize < np.dtype(dt).itemsize) + result = tdt(image) + 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) def img_as_float(image): @@ -103,8 +204,7 @@ def img_as_float(image): Negative input values will be shifted to the positive domain. """ - prec_loss = () - return _convert(image, np.float64, prec_loss) + return _convert(image, np.float64) def img_as_uint(image): @@ -125,9 +225,7 @@ def img_as_uint(image): Negative input values will be shifted to the positive domain. """ - - prec_loss = (np.float32, np.float64) - return _convert(image, np.uint16, prec_loss) + return _convert(image, np.uint16) def img_as_int(image): @@ -149,8 +247,7 @@ def img_as_int(image): the output image will still only have positive values. """ - prec_loss = (np.float32, np.float64, np.uint16) - return _convert(image, np.int16, prec_loss) + return _convert(image, np.int16) def img_as_ubyte(image): @@ -172,5 +269,4 @@ def img_as_ubyte(image): 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) + return _convert(image, np.uint8) From af01a4127933e9dacfec49559d28a861520e6b62 Mon Sep 17 00:00:00 2001 From: cgohlke Date: Mon, 12 Dec 2011 22:04:16 -0800 Subject: [PATCH 2/2] Merged some code paths --- skimage/util/dtype.py | 183 +++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 111 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index f573dda9..da2ff5e7 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -68,121 +68,82 @@ def _convert(image, dtype): if itemsize_in > itemsize: prec_loss() return dtype(image) - if kind == 'u': - # floating point -> unsigned integer + # floating point -> integer + prec_loss() + image = np.array(image, dtype=np.promote_types(dtype_in, dtype)) + image *= np.iinfo(dtype).max + 1 + np.clip(image, 0, np.iinfo(dtype).max, out=image) + return dtype(image) + if kind == 'f': + # integer -> floating point + if itemsize_in >= itemsize: prec_loss() - tdt = next(dt for dt in (dtype_in, np.float32, np.float64) - if itemsize * 8 < np.finfo(dt).nmant) - image = tdt(image) - umax = np.iinfo(dtype).max - image = image * (umax + 1) - np.clip(image, 0, umax, out=image) - return dtype(image) - if kind == 'i': - # floating point -> signed integer - prec_loss() - tdt = next(dt for dt in (dtype_in, np.float32, np.float64) - if itemsize * 8 < np.finfo(dt).nmant) - image = tdt(image) - image = image * (np.iinfo(dtype).max + 1) - np.clip(image, 0, np.iinfo(dtype).max, out=image) - return dtype(image) + image = np.array(image, dtype=np.promote_types(dtype_in, dtype)) + 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': - if kind == 'f': - # unsigned integer -> floating point - if itemsize_in * 8 > np.finfo(dtype).nmant: - prec_loss() - tdt = next(dt for dt in (dtype, np.float32, np.float64) - if itemsize_in * 8 < np.finfo(dt).nmant) - image = tdt(image) - image /= np.iinfo(dtype_in).max + # 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) - if kind == 'u': - # unsigned integer -> unsigned integer - if itemsize_in > itemsize: - prec_loss() - image = image >> 8 * (itemsize_in - itemsize) - return dtype(image) - else: - result = dtype(image) - result <<= 8 * (itemsize - itemsize_in) - if itemsize - itemsize_in == 3: - # uint8 -> uint32 - image = dtype(image) - image *= 2**16 + 2**8 + 1 - result += image - return result - if kind == 'i': - # unsigned integer -> signed integer - if itemsize_in >= itemsize: - prec_loss() - image = image >> (8 * (itemsize_in - itemsize) + 1) - return dtype(image) - else: - result = dtype(image) - result <<= 8 * (itemsize - itemsize_in) - 1 - if itemsize - itemsize_in == 3: - # uint8 -> int32 - image = dtype(image) - image *= 2**16 + 2**8 + 1 - result += image >> 1 - return dtype(result) - if kind_in == 'i': - if kind == 'f': - # signed integer -> floating point - if itemsize_in * 8 > np.finfo(dtype).nmant: - prec_loss() - sign_loss() - tdt = next(dt for dt in (dtype, np.float32, np.float64) - if itemsize_in * 8 < np.finfo(dt).nmant) - image = tdt(image) - image -= np.iinfo(dtype_in).min - image /= np.iinfo(dtype_in).max - np.iinfo(dtype_in).min + 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 + else: + result += image + return dtype(result) + if kind == 'u': + # signed integer -> unsigned integer + sign_loss() + image = np.array(image, dtype=np.promote_types(dtype_in, dtype)) + image -= np.iinfo(dtype_in).min + if itemsize_in == itemsize: return dtype(image) - if kind == 'u': - # signed integer -> unsigned integer - sign_loss() - tdt = next(dt for dt in (np.int16, np.int32, np.int64) - if itemsize_in < np.dtype(dt).itemsize) - image = tdt(image) - 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) - else: - 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 += image - return result - if kind == 'i': - # signed integer -> signed integer - if itemsize_in > itemsize: - prec_loss() - image = image // 2**(8 * (itemsize_in - itemsize)) - return dtype(image) - else: - tdt = next(dt for dt in (dtype, np.int16, np.int32, np.int64) - if itemsize_in < np.dtype(dt).itemsize) - image = tdt(image) - image -= np.iinfo(dtype_in).min - tdt = next(dt for dt in (np.int32, np.int64) - if image.dtype.itemsize < np.dtype(dt).itemsize) - result = tdt(image) - 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) + 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 += image + return result + if kind == 'i': + # signed integer -> signed integer + if itemsize_in > itemsize: + prec_loss() + return dtype(image // 2**(8 * (itemsize_in - itemsize))) + # upcast to next higher precision signed integer type + dt = next(dt for dt in (np.int16, np.int32, np.int64) + if image.itemsize < np.dtype(dt).itemsize) + image = np.array(image, dtype=dt) + image -= np.iinfo(dtype_in).min + # upcast to next higher precision signed integer type + dt = next(dt for dt in (np.int32, np.int64) + if image.itemsize < np.dtype(dt).itemsize) + result = np.array(image, dtype=dt) + 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) def img_as_float(image):