diff --git a/CHANGELOG.md b/CHANGELOG.md index 052b8b62d..eda0bf72b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - Infer DICOM file size, when possible ([#1448](../../pull/1448)) - Swap styles faster in the frame viewer ([#1452](../../pull/1452)) +### Bug Fixes +- Fix an issue with compositing sources in the multi source caused by alpha range ([#1453](../../pull/1453)) + ## 1.27.1 ### Improvements diff --git a/large_image/tilesource/utilities.py b/large_image/tilesource/utilities.py index 01e24de2b..e6f2527be 100644 --- a/large_image/tilesource/utilities.py +++ b/large_image/tilesource/utilities.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast import numpy as np +import numpy.typing as npt import PIL import PIL.Image import PIL.ImageColor @@ -729,7 +730,7 @@ def getAvailableNamedPalettes(includeColors: bool = True, reduced: bool = False) return sorted(palettes) -def fullAlphaValue(arr: np.ndarray) -> int: +def fullAlphaValue(arr: Union[np.ndarray, npt.DTypeLike]) -> int: """ Given a numpy array, return the value that should be used for a fully opaque alpha channel. For uint variants, this is the max value. @@ -737,8 +738,9 @@ def fullAlphaValue(arr: np.ndarray) -> int: :param arr: a numpy array. :returns: the value for the alpha channel. """ - if arr.dtype.kind == 'u': - return np.iinfo(arr.dtype).max + dtype = arr.dtype if isinstance(arr, np.ndarray) else arr + if dtype.kind == 'u': + return np.iinfo(dtype).max return 1 diff --git a/sources/multi/large_image_source_multi/__init__.py b/sources/multi/large_image_source_multi/__init__.py index 47cf5b060..b2d8a29d5 100644 --- a/sources/multi/large_image_source_multi/__init__.py +++ b/sources/multi/large_image_source_multi/__init__.py @@ -931,36 +931,34 @@ def _mergeTiles(self, base, tile, x, y): return tile if base is None: base = np.zeros((0, 0, tile.shape[2]), dtype=tile.dtype) - if tile.shape[2] in {2, 4}: - base[:, :, -1] = fullAlphaValue(tile) base, tile = _makeSameChannelDepth(base, tile) if base.shape[0] < tile.shape[0] + y: vfill = np.zeros( (tile.shape[0] + y - base.shape[0], base.shape[1], base.shape[2]), dtype=base.dtype) if base.shape[2] in {2, 4}: - vfill[:, :, -1] = fullAlphaValue(base) + vfill[:, :, -1] = fullAlphaValue(self.dtype) base = np.vstack((base, vfill)) if base.shape[1] < tile.shape[1] + x: hfill = np.zeros( (base.shape[0], tile.shape[1] + x - base.shape[1], base.shape[2]), dtype=base.dtype) if base.shape[2] in {2, 4}: - hfill[:, :, -1] = fullAlphaValue(base) + hfill[:, :, -1] = fullAlphaValue(self.dtype) base = np.hstack((base, hfill)) if base.flags.writeable is False: base = base.copy() if base.shape[2] in {2, 4}: baseA = base[y:y + tile.shape[0], x:x + tile.shape[1], -1].astype( - float) / fullAlphaValue(base) - tileA = tile[:, :, -1].astype(float) / fullAlphaValue(tile) + float) / fullAlphaValue(self.dtype) + tileA = tile[:, :, -1].astype(float) / fullAlphaValue(self.dtype) outA = tileA + baseA * (1 - tileA) base[y:y + tile.shape[0], x:x + tile.shape[1], :-1] = ( - tile[:, :, :-1] * tileA[..., np.newaxis] + + np.where(tileA[..., np.newaxis], tile[:, :, :-1], 0) + base[y:y + tile.shape[0], x:x + tile.shape[1], :-1] * baseA[..., np.newaxis] * (1 - tileA[..., np.newaxis]) - ) / outA[..., np.newaxis] - base[y:y + tile.shape[0], x:x + tile.shape[1], -1] = outA * fullAlphaValue(base) + ) / np.where(outA[..., np.newaxis], outA[..., np.newaxis], 1) + base[y:y + tile.shape[0], x:x + tile.shape[1], -1] = outA * fullAlphaValue(self.dtype) else: base[y:y + tile.shape[0], x:x + tile.shape[1], :] = tile return base @@ -1030,8 +1028,9 @@ def _getTransformedTile(self, ts, transform, corners, scale, frame, crop=None): } if output['maxWidth'] <= 0 or output['maxHeight'] <= 0: return None, 0, 0 - srcImage = ts.getRegion( - format=TILE_FORMAT_NUMPY, region=region, output=output, frame=frame)[0] + srcImage, _ = ts.getRegion( + region=region, output=output, frame=frame, + format=TILE_FORMAT_NUMPY) # This is the region we actually took in our source coordinates, scaled # for if we took a low res version regioncorners = np.array([