From 21cff7dc7d9e4135ada561c71a7b148c5e7c62c8 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Mon, 12 Sep 2022 12:46:40 -0400 Subject: [PATCH] Add a module for style functions. --- docs/tilesource_options.rst | 2 +- large_image/tilesource/stylefuncs.py | 39 ++++++++++++++++++++++++++++ large_image/tilesource/utilities.py | 36 ------------------------- test/test_source_base.py | 10 +++---- 4 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 large_image/tilesource/stylefuncs.py diff --git a/docs/tilesource_options.rst b/docs/tilesource_options.rst index bcc5a19b6..033fce9ac 100644 --- a/docs/tilesource_options.rst +++ b/docs/tilesource_options.rst @@ -80,7 +80,7 @@ A band definition is an object which can contain the following keys: The function parameter can be a single function or a list of functions. Items in a list of functions can, themselves, be lists of functions. A single function can be an object or a string. If a string, this is shorthand for ``{"name": }``. The function object contains (all but ``name`` are optional): - - ``name``: The name of a Python module and function that is installed in the same environment as large_image. For instance, ``large_image.tilesource.utilities.maskPixelValues`` will use the function ``maskPixelValues`` in the ``large_image.tilesource.utilities`` module. The function must be a Python function that takes a numpy array as the first parameter (the image) and has named parameters or kwargs for any passed parameters and possibly the style context. + - ``name``: The name of a Python module and function that is installed in the same environment as large_image. For instance, ``large_image.tilesource.stylefuncs.maskPixelValues`` will use the function ``maskPixelValues`` in the ``large_image.tilesource.stylefuncs`` module. The function must be a Python function that takes a numpy array as the first parameter (the image) and has named parameters or kwargs for any passed parameters and possibly the style context. - ``parameters``: A dictionary of parameters to pass to the function. diff --git a/large_image/tilesource/stylefuncs.py b/large_image/tilesource/stylefuncs.py new file mode 100644 index 000000000..16f1b0b11 --- /dev/null +++ b/large_image/tilesource/stylefuncs.py @@ -0,0 +1,39 @@ +# This module contains functions for use in styles + +import numpy + + +def maskPixelValues(image, context, values=None, negative=None, positive=None): + """ + This is a style utility function that returns a black-and-white 8-bit image + where the image is white if the pixel of the source image is in a list of + values and black otherwise. The values is a list where each entry can + either be a tuple the same length as the band dimension of the output image + or a single value which is handled as 0xBBGGRR. + + :param image: a numpy array of Y, X, Bands. + :param context: the style context. context.image is the source image + :param values: an array of values, each of which is either an array of the + same number of bands as the source image or a single value of the form + 0xBBGGRR assuming uint8 data. + :param negative: None to use [0, 0, 0, 255], or an RGBA uint8 value for + pixels not in the value list. + :param positive: None to use [255, 255, 255, 0], or an RGBA uint8 value for + pixels in the value list. + :returns: an RGBA numpy image which is exactly black or transparent white. + """ + src = context.image + mask = numpy.full(src.shape[:2], False) + for val in values: + if not isinstance(val, (list, tuple)): + if src.shape[-1] == 1: + val = [val] + else: + val = [val % 256, val // 256 % 256, val // 65536 % 256] + val = (list(val) + [255] * src.shape[2])[:src.shape[2]] + match = numpy.array(val) + mask = mask | (src == match).all(axis=-1) + image[mask != True] = negative or [0, 0, 0, 255] # noqa E712 + image[mask] = positive or [255, 255, 255, 0] + image = image.astype(numpy.uint8) + return image diff --git a/large_image/tilesource/utilities.py b/large_image/tilesource/utilities.py index aaad32846..9f19f021c 100644 --- a/large_image/tilesource/utilities.py +++ b/large_image/tilesource/utilities.py @@ -935,42 +935,6 @@ def histogramThreshold(histogram, threshold, fromMax=False): return edges[-1] -def maskPixelValues(image, context, values=None, negative=None, positive=None): - """ - This is a style utility function that returns a black-and-white 8-bit image - where the image is white if the pixel of the source image is in a list of - values and black otherwise. The values is a list where each entry can - either be a tuple the same length as the band dimension of the output image - or a single value which is handled as 0xBBGGRR. - - :param image: a numpy array of Y, X, Bands. - :param context: the style context. context.image is the source image - :param values: an array of values, each of which is either an array of the - same number of bands as the source image or a single value of the form - 0xBBGGRR assuming uint8 data. - :param negative: None to use [0, 0, 0, 255], or an RGBA uint8 value for - pixels not in the value list. - :param positive: None to use [255, 255, 255, 0], or an RGBA uint8 value for - pixels in the value list. - :returns: an RGBA numpy image which is exactly black or transparent white. - """ - src = context.image - mask = numpy.full(src.shape[:2], False) - for val in values: - if not isinstance(val, (list, tuple)): - if src.shape[-1] == 1: - val = [val] - else: - val = [val % 256, val // 256 % 256, val // 65536 % 256] - val = (list(val) + [255] * src.shape[2])[:src.shape[2]] - match = numpy.array(val) - mask = mask | (src == match).all(axis=-1) - image[mask != True] = negative or [0, 0, 0, 255] # noqa E712 - image[mask] = positive or [255, 255, 255, 0] - image = image.astype(numpy.uint8) - return image - - def addPILFormatsToOutputOptions(): """ Check PIL for available formats that be saved and add them to the lists of diff --git a/test/test_source_base.py b/test/test_source_base.py index 65862079a..dc685d7c2 100644 --- a/test/test_source_base.py +++ b/test/test_source_base.py @@ -574,7 +574,7 @@ def testStyleFunctions(): format=large_image.constants.TILE_FORMAT_NUMPY) sourceFunc2 = large_image.open(imagePath, style={ 'function': { - 'name': 'large_image.tilesource.utilities.maskPixelValues', + 'name': 'large_image.tilesource.stylefuncs.maskPixelValues', 'context': True, 'parameters': {'values': [164, 165]}}, 'bands': []}) @@ -584,7 +584,7 @@ def testStyleFunctions(): assert numpy.any(region2 != region1) sourceFunc3 = large_image.open(imagePath, style={ 'function': { - 'name': 'large_image.tilesource.utilities.maskPixelValues', + 'name': 'large_image.tilesource.stylefuncs.maskPixelValues', 'context': True, 'parameters': {'values': [[63, 63, 63]]}}, 'bands': []}) @@ -594,7 +594,7 @@ def testStyleFunctions(): assert numpy.any(region3 != region2) sourceFunc4 = large_image.open(imagePath, style={ 'function': [{ - 'name': 'large_image.tilesource.utilities.maskPixelValues', + 'name': 'large_image.tilesource.stylefuncs.maskPixelValues', 'context': 'context', 'parameters': {'values': [164, 165]}}], 'bands': []}) @@ -608,7 +608,7 @@ def testStyleFunctionsWarnings(): imagePath = datastore.fetch('extraoverview.tiff') source = large_image.open(imagePath, style={ 'function': { - 'name': 'large_image.tilesource.utilities.maskPixelValues', + 'name': 'large_image.tilesource.stylefuncs.maskPixelValues', 'context': True, 'parameters': {'values': ['bad value']}}, 'bands': []}) @@ -630,7 +630,7 @@ def testStyleFunctionsWarnings(): source = large_image.open(imagePath, style={ 'function': { - 'name': 'large_image.tilesource.utilities.noSuchFunction', + 'name': 'large_image.tilesource.stylefuncs.noSuchFunction', 'context': True, 'parameters': {'values': [100]}}, 'bands': []})