Skip to content

Commit

Permalink
Tweaks to ensure self tests pass with latest packages
Browse files Browse the repository at this point in the history
  • Loading branch information
gb119 committed Jul 6, 2024
1 parent 79c0562 commit 286fcf2
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 88 deletions.
4 changes: 1 addition & 3 deletions Stoner/Image/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,7 @@ def _load_tiff(cls, filename, **kargs): # pylint: disable=unused-argument
with Image.open(filename, "r") as img:
image = np.asarray(img)
if image.ndim == 3:
if image.shape[2] < 4: # Need to add a dummy alpha channel
image = np.append(np.zeros_like(image[:, :, 0]), axis=2)
image = image.view(dtype=np.uint32).reshape(image.shape[:-1])
image = io.imread(filename).view(np.uint32)[:, :, 0] # Workaround for issues with more recent pillow
tags = img.tag_v2
if 270 in tags:
from json import loads
Expand Down
59 changes: 28 additions & 31 deletions Stoner/Image/imagefuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,47 +348,44 @@ def convert(image, dtype, force_copy=False, uniform=False, normalise=True):
if not (dtype_in in _supported_types and dtype in _supported_types):
raise ValueError(f"can not convert {dtype_in} to {dtype}.")

kind = dtypeobj.kind
kind_in = dtypeobj_in.kind
itemsize = dtypeobj.itemsize
itemsize_in = dtypeobj_in.itemsize

if kind == "b":
if dtypeobj.kind == "b":
# to binary image
if kind_in in "fi":
if dtypeobj_in.kind in "fi":
sign_loss(dtype_in, dtypeobj)
prec_loss(dtypeobj_in, dtypeobj)
return image > dtype_in(dtype_range[dtype_in][1] / 2)

if kind_in == "b":
if dtypeobj_in.kind == "b":
# from binary image, to float and to integer
result = np.where(~image, *dtype_range[dtype])
return result

if kind in "ui":
if dtypeobj.kind in "ui":
imin = np.iinfo(dtype).min
imax = np.iinfo(dtype).max
if kind_in in "ui":
if dtypeobj_in.kind in "ui":
imin_in = np.iinfo(dtype_in).min
imax_in = np.iinfo(dtype_in).max

if kind_in == "f":
if dtypeobj_in.kind == "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":
if dtypeobj.kind == "f":
# floating point -> floating point
if itemsize_in > itemsize:
if dtypeobj_in.itemsize > dtypeobj.itemsize:
prec_loss(dtypeobj_in, dtypeobj)
return image.astype(dtype)

# floating point -> integer
prec_loss(dtypeobj_in, dtypeobj)
# use float type that can represent output integer type
image = np.array(image, _dtype(itemsize, dtype_in, np.float32, np.float64))
image = np.array(image, _dtype(dtypeobj.itemsize, dtype_in, np.float32, np.float64))
if not uniform:
if kind == "u":
if dtypeobj.kind == "u":
image *= imax
elif itemsize_in <= itemsize and itemsize == 8: # f64->int64 needs care to avoid overruns!
elif (
dtypeobj_in.itemsize <= dtypeobj.itemsize and dtypeobj.itemsize == 8
): # f64->int64 needs care to avoid overruns!
image *= 2**54 # float64 has 52bits of mantissa, so this will avoid precission loss for a +/-1 range
np.rint(image, out=image)
image = image.astype(dtype)
Expand All @@ -400,7 +397,7 @@ def convert(image, dtype, force_copy=False, uniform=False, normalise=True):
image /= 2.0
np.rint(image, out=image)
np.clip(image, imin, imax, out=image)
elif kind == "u":
elif dtypeobj.kind == "u":
image *= imax + 1
np.clip(image, 0, imax, out=image)
else:
Expand All @@ -409,14 +406,14 @@ def convert(image, dtype, force_copy=False, uniform=False, normalise=True):
np.clip(image, imin, imax, out=image)
return image.astype(dtype)

if kind == "f":
if dtypeobj.kind == "f":
# integer -> floating point
if itemsize_in >= itemsize:
if dtypeobj_in.itemsize >= dtypeobj.itemsize:
prec_loss(dtypeobj_in, dtypeobj)
# use float type that can exactly represent input integers
image = np.array(image, _dtype(itemsize_in, dtype, np.float32, np.float64))
image = np.array(image, _dtype(dtypeobj_in.itemsize, dtype, np.float32, np.float64))
if normalise: # normalise floats by maximum value of int type
if kind_in == "u":
if dtypeobj_in.kind == "u":
image /= imax_in
# DirectX uses this conversion also for signed ints
# if imin_in:
Expand All @@ -427,28 +424,28 @@ def convert(image, dtype, force_copy=False, uniform=False, normalise=True):
image /= imax_in - imin_in
return image.astype(dtype)

if kind_in == "u":
if kind == "i":
if dtypeobj_in.kind == "u":
if dtypeobj.kind == "i":
# unsigned integer -> signed integer
image = im_scale(image, 8 * itemsize_in, 8 * itemsize - 1, dtypeobj_in, dtypeobj)
image = im_scale(image, 8 * dtypeobj_in.itemsize, 8 * dtypeobj.itemsize - 1, dtypeobj_in, dtypeobj)
return image.view(dtype)
# unsigned integer -> unsigned integer
return im_scale(image, 8 * itemsize_in, 8 * itemsize, dtypeobj_in, dtypeobj)
return im_scale(image, 8 * dtypeobj_in.itemsize, 8 * dtypeobj.itemsize, dtypeobj_in, dtypeobj)

if kind == "u":
if dtypeobj.kind == "u":
# signed integer -> unsigned integer
sign_loss(dtype_in, dtypeobj)
image = im_scale(image, 8 * itemsize_in - 1, 8 * itemsize, dtypeobj_in, dtypeobj)
image = im_scale(image, 8 * dtypeobj_in.itemsize - 1, 8 * dtypeobj.itemsize, dtypeobj_in, dtypeobj)
result = np.empty(image.shape, dtype)
np.maximum(image, 0, out=result, dtype=image.dtype, casting="unsafe")
return result

# signed integer -> signed integer
if itemsize_in > itemsize:
return im_scale(image, 8 * itemsize_in - 1, 8 * itemsize - 1, dtypeobj_in, dtypeobj)
image = image.astype(_dtype2("i", itemsize * 8))
if dtypeobj_in.itemsize > dtypeobj.itemsize:
return im_scale(image, 8 * dtypeobj_in.itemsize - 1, 8 * dtypeobj.itemsize - 1, dtypeobj_in, dtypeobj)
image = image.astype(_dtype2("i", dtypeobj.itemsize * 8))
image -= imin_in
image = im_scale(image, 8 * itemsize_in, 8 * itemsize, dtypeobj_in, dtypeobj, copy=False)
image = im_scale(image, 8 * dtypeobj_in.itemsize, 8 * dtypeobj.itemsize, dtypeobj_in, dtypeobj, copy=False)
image += imin
return image.astype(dtype)

Expand Down
105 changes: 55 additions & 50 deletions Stoner/Image/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,58 +78,63 @@ def _dtype2(kind, bits, itemsize=1):
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 = "int%s" % mnew
else:
dtype = "uint%s" % mnew
n = int(np.ceil(n / 2) * 2)
msg = "Downcasting %s to %s without scaling because max " "value %s fits in %s" % (
a.dtype,
dtype,
a.max(),
dtype,
)
def _scale(image, src_bits, dest_bits, dtypeobj_in, dtypeobj, copy=True):
"""Scale unsigned/positive integers from src_bitsto dest_bits_bits.
Numbers can be represented exactly only if dest_bits_is a multiple of n
Output array is of same kind as input.
"""
if src_bits == dest_bits: # Trivial case no scale necessary
return image.copy() if copy else image

if src_bits > dest_bits:
return _down_scale(image, src_bits, dest_bits, dtypeobj_in, dtypeobj)

return _up_scale(image, src_bits, dest_bits, dtypeobj_in, dtypeobj)


def _down_scale(image, src_bits, dest_bits, dtypeobj_in, dtypeobj, copy=True):
"""Downscale and image from src_bits to dest_bits."""
kind = image.dtype.kind
if image.max() <= 2**dest_bits:
prefix = ["uint", "int"][dest_bits % 2]
dest_bits += dest_bits % 2
dtype = f"{prefix}{dest_bits}"
src_bits += src_bits % 2
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
warn(
f"Downcasting {image.dtype} to {dtype} without scaling"
+ f"because max value {image.max()} fits in {dtype}."
)
return image.astype(_dtype2(kind, dest_bits))
prec_loss(dtypeobj_in, dtypeobj)
if copy:
image2 = np.empty(image.shape, _dtype2(kind, dest_bits))
else:
image2 = image
np.floor_divide(image, 2 ** (src_bits - dest_bits), out=image2, dtype=image.dtype, casting="unsafe")
return image2


def _up_scale(image, src_bits, dest_bits, dtypeobj_in, dtypeobj, copy=True):
"""Upscale an image from src_bits to dest_bits."""
kind = image.dtype.kind
if dest_bits % src_bits == 0:
# exact upscale to a multiple of src_bitsbits
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,
image2 = np.empty(image.shape, _dtype2(kind, dest_bits))
else:
image2 = np.array(image, _dtype2(kind, dest_bits, image.dtype.itemsize), copy=False)
np.multiply(image, (2**dest_bits - 1) // (2**src_bits - 1), out=image2, dtype=image2.dtype)
return image2
# upscale to a multiple of src_bitsbits,
# then downscale with precision loss
prec_loss(dtypeobj_in, dtypeobj)
o = (m // n + 1) * n
upscale_factor = (dest_bits // src_bits + 1) * src_bits
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
image2 = np.empty(image.shape, _dtype2(kind, upscale_factor))
else:
image2 = image
np.multiply(image, (2**upscale_factor - 1) // (2**src_bits - 1), out=image2, dtype=image2.dtype)
image2 //= 2 ** (upscale_factor - dest_bits)
return image2
8 changes: 4 additions & 4 deletions tests/Stoner/Image/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ def test_load_save_all():
iml = ImageFile(path.join(tmpdir, "kermit-{}.tiff".format(fmt)))
del iml["Loaded from"]
assert ims[fmt] == iml, f"{ims[fmt].metadata} {iml.metadata}"
iml = ImageFile(path.join(tmpdir, "kermit-{}.npy".format(fmt)))
iml = ImageFile(path.join(tmpdir, f"kermit-{fmt}.npy"))
del iml["Loaded from"]
assert np.all(ims[fmt].data == iml.data), "Round tripping npy with format {} failed".format(fmt)
assert np.all(ims[fmt].data == iml.data), f"Round tripping npy with format {fmt} failed"
if fmt != "uint16":
im = ImageFile(path.join(tmpdir, "kermit-nometadata-{}.tiff".format(fmt)))
assert np.all(im.data == ims[fmt].data), "Loading from tif without metadata failed for {}".format(fmt)
im = ImageFile(path.join(tmpdir, f"kermit-nometadata-{fmt}.tiff"))
assert np.all(im.data == ims[fmt].data), f"Loading from tif without metadata failed for {fmt}"
shutil.rmtree(tmpdir)
_ = image.convert("uint8")

Expand Down

0 comments on commit 286fcf2

Please sign in to comment.