# -*- coding: utf-8 -*-
"""Code adapted from skimage module."""
from __future__ import division
__all__ = ["sign_loss", "prec_loss", "dtype_range", "_dtype", "_dtype2"]
from warnings import warn
import numpy as np
from ..tools.classes import Options
dtype_range = {
np.bool_: (False, True),
np.uint8: (0, 255),
np.uint16: (0, 65535),
np.int8: (-128, 127),
np.int16: (-32768, 32767),
np.int64: (-(2**63), 2**63 - 1),
np.uint64: (0, 2**64 - 1),
np.int32: (-(2**31), 2**31 - 1),
np.uint32: (0, 2**32 - 1),
np.float32: (-1.0, 1.0),
np.float64: (-1.0, 1.0),
}
if float(np.version.version.split(".")[1]) < 1.24:
dtype_range[np.bool8] = (False, True)
integer_types = (np.uint8, np.uint16, np.int8, np.int16)
_supported_types = (
np.bool_,
np.uint8,
np.uint16,
np.uint32,
np.uint64,
np.int8,
np.int16,
np.int32,
np.int64,
np.float32,
np.float64,
)
dtype_range[np.float16] = (-1, 1)
_supported_types += (np.float16,)
[docs]def sign_loss(dtypeobj_in, dtypeobj):
"""Warn over loss of sign information when converting image."""
if Options().warnings:
warn(
"Possible sign loss when converting negative image of type "
f"{dtypeobj_in} to positive image of type {dtypeobj}."
)
[docs]def prec_loss(dtypeobj_in, dtypeobj):
"""Warn over precision loss when converting image."""
if Options().warnings:
warn(f"Possible precision loss when converting from {dtypeobj_in} to {dtypeobj}")
[docs]def _dtype(itemsize, *dtypes):
"""Return first of `dtypes` with itemsize greater than `itemsize."""
try:
ret = next(dt for dt in dtypes if itemsize < np.dtype(dt).itemsize)
except StopIteration:
ret = dtypes[0]
return ret
[docs]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, dtypeobj_in, dtypeobj, copy=True):
"""Scaleunsigned/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 and a.max() <= 2**m:
mnew = int(np.ceil(m / 2) * 2)
if mnew > m:
dtype = f"int{mnew}"
else:
dtype = f"uint{mnew}"
n = int(np.ceil(n / 2) * 2)
msg = f"Downcasting {a.dtype} to {dtype} without scaling because max value {a.max()} fits in{dtype}"
if Options().warnings:
warn(msg)
return a.astype(_dtype2(kind, m))
if n == m:
return a.copy() if copy else a
if n > m:
# downscale with precision loss
prec_loss(dtypeobj_in, dtypeobj)
if copy:
b = np.empty(a.shape, _dtype2(kind, m))
np.floor_divide(a, 2 ** (n - m), out=b, dtype=a.dtype, casting="unsafe")
return b
a //= 2 ** (n - m)
return a
if 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
a = np.array(a, _dtype2(kind, m, a.dtype.itemsize), copy=False)
a *= (2**m - 1) // (2**n - 1)
return a
# upscale to a multiple of n bits,
# then downscale with precision loss
prec_loss(dtypeobj_in, dtypeobj)
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
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