Skip to content

Commit

Permalink
Merge pull request #883 from girder/style-minmax-full
Browse files Browse the repository at this point in the history
Support more style range options
  • Loading branch information
manthey authored Jul 5, 2022
2 parents 143e8f7 + 486784c commit 959f50d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 7 deletions.
8 changes: 6 additions & 2 deletions docs/tilesource_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ A band definition is an object which can contain the following keys:

- ``framedelta``: if specified, and ``frame`` is not specified, override the frame parameter used in the tile query for this band by adding the value to the current frame number. If many different frames are being requested, all with the same ``framedelta``, this is more efficient than varying the ``frame`` within the style.

- ``min``: the value to map to the first palette value. Defaults to 0. 'auto' to use 0 if the reported minimum and maximum of the band are between [0, 255] or use the reported minimum otherwise. 'min' or 'max' to always uses the reported minimum or maximum. 'min:<threshold>' and 'max:<threshold>' pick a value that excludes a threshold amount from the histogram; for instance, 'min:0.02' would exclude at most the dimmest 2% of values by using an appropriate value for the minimum based on a computed histogram with some default binning options. 'auto:<threshold>' works like auto, though it applies the threshold if the reported minimum would otherwise be used.
- ``min``: the value to map to the first palette value. Defaults to 0. 'auto' to use 0 if the reported minimum and maximum of the band are between [0, 255] or use the reported minimum otherwise. 'min' or 'max' to always uses the reported minimum or maximum. 'min:<threshold>' and 'max:<threshold>' pick a value that excludes a threshold amount from the histogram; for instance, 'min:0.02' would exclude at most the dimmest 2% of values by using an appropriate value for the minimum based on a computed histogram with some default binning options. 'auto:<threshold>' works like auto, though it applies the threshold if the reported minimum would otherwise be used. 'full' is the same as specifying 0.

- ``max``: the value to map to the last palette value. Defaults to 255. 'auto' to use 0 if the reported minimum and maximum of the band are between [0, 255] or use the reported maximum otherwise. 'min' or 'max' to always uses the reported minimum or maximum. 'min:<threshold>' and 'max:<threshold>' pick a value that excludes a threshold amount from the histogram; for instance, 'max:0.02' would exclude at most the brightest 2% of values by using an appropriate value for the maximum based on a computed histogram with some default binning options. 'auto:<threshold>' works like auto, though it applies the threshold if the reported maximum would otherwise be used.
- ``max``: the value to map to the last palette value. Defaults to 255. 'auto' to use 0 if the reported minimum and maximum of the band are between [0, 255] or use the reported maximum otherwise. 'min' or 'max' to always uses the reported minimum or maximum. 'min:<threshold>' and 'max:<threshold>' pick a value that excludes a threshold amount from the histogram; for instance, 'max:0.02' would exclude at most the brightest 2% of values by using an appropriate value for the maximum based on a computed histogram with some default binning options. 'auto:<threshold>' works like auto, though it applies the threshold if the reported maximum would otherwise be used. 'full' uses a value based on the data type of the band. This will be 1 for a float data type and 65535 for a uint16 datatype.

- ``palette``: This is a list or two or more colors. The values between min and max are interpolated using a piecewise linear algorithm or a nearest value algorithm (depending on the ``scheme``) to map to the specified palette values. It can be specified in a variety of ways:
- a list of two or more color values, where the color values are css-style strings (e.g., of the form #RRGGBB, #RRGGBBAA, #RGB, #RGBA, or a css ``rgb``, ``rgba``, ``hsl``, or ``hsv`` string, or a css color name), or, if matplotlib is available, a matplotlib color name, or a list or tuple of RGB(A) values on a scale of [0-1].
Expand All @@ -58,6 +58,10 @@ A band definition is an object which can contain the following keys:

- ``clamp``: either True to clamp (also called clip or crop) values outside of the [min, max] to the ends of the palette or False to make outside values transparent.

- ``dtype``: if specified, cast the intermediate results to this data type. Only the first such value is used, and this can be specified as a base key if ``bands`` is specified. Normally, if a style is applied, the intermediate data is a numpy float array with values from [0,255]. If this is ``uint16``, the results are multiplied by 65535 / 255 and cast to that dtype. If ``float``, the results are divided by 255.

- ``axis``: if specified, keep on the specified axis (channel) of the intermediate numpy array. This is typically between 0 and 3 for the red, green, blue, and alpha channels. Only the first such value is used, and this can be specified as a base key if ``bands`` is specified.

Note that some tile sources add additional options to the ``style`` parameter.

Examples
Expand Down
40 changes: 35 additions & 5 deletions large_image/tilesource/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ def __init__(self, encoding='JPEG', jpegQuality=95, jpegSubsampling=0,
0. 'auto' to use 0 if the reported minimum and maximum of
the band are between [0, 255] or use the reported minimum
otherwise. 'min' or 'max' to always uses the reported
minimum or maximum.
minimum or maximum. 'full' to always use 0.
:max: the value to map to the last palette value. Defaults to
255. 'auto' to use 0 if the reported minimum and maximum
of the band are between [0, 255] or use the reported
maximum otherwise. 'min' or 'max' to always uses the
reported minimum or maximum.
reported minimum or maximum. 'full' to use the maximum
value of the base data type (either 1, 255, or 65535).
:palette: a list of two or more color strings, where color
strings are of the form #RRGGBB, #RRGGBBAA, #RGB, #RGBA, or
any string parseable by the PIL modules, or, if it is
Expand All @@ -101,11 +102,21 @@ def __init__(self, encoding='JPEG', jpegQuality=95, jpegSubsampling=0,
:clamp: either True to clamp (also called clip or crop) values
outside of the [min, max] to the ends of the palette or
False to make outside values transparent.
:dtype: convert the results to the specified numpy dtype.
Normally, if a style is applied, the results are
intermediately a float numpy array with a value range of
[0,255]. If this is 'uint16', it will be cast to that and
multiplied by 65535/255. If 'float', it will be divided by
255.
:axis: keep only the specified axis from the numpy intermediate
results. This can be used to extract a single channel
after compositing.
Alternately, the style object can contain a single key of 'bands',
which has a value which is a list of style dictionaries as above,
excepting that each must have a band that is not -1. Bands are
composited in the order listed.
composited in the order listed. This base object may also contain
the 'dtype' and 'axis' values.
"""
self.logger = config.getConfig('logger')
self.cache, self.cache_lock = getTileCache()
Expand Down Expand Up @@ -1023,7 +1034,7 @@ def _validateMinMaxValue(self, value, frame, dtype):
:returns: the validated value and a threshold from [0-1].
"""
threshold = 0
if value not in {'min', 'max', 'auto'}:
if value not in {'min', 'max', 'auto', 'full'}:
try:
if ':' in str(value) and value.split(':', 1)[0] in {'min', 'max', 'auto'}:
threshold = float(value.split(':', 1)[1])
Expand All @@ -1039,7 +1050,7 @@ def _validateMinMaxValue(self, value, frame, dtype):
self._scanForMinMax(dtype, frame, onlyMinMax=not threshold)
return value, threshold

def _getMinMax(self, minmax, value, dtype, bandidx=None, frame=None):
def _getMinMax(self, minmax, value, dtype, bandidx=None, frame=None): # noqa
"""
Get an appropriate minimum or maximum for a band.
Expand All @@ -1057,6 +1068,15 @@ def _getMinMax(self, minmax, value, dtype, bandidx=None, frame=None):
"""
frame = frame or 0
value, threshold = self._validateMinMaxValue(value, frame, dtype)
if value == 'full':
value = 0
if minmax != 'min':
if dtype == numpy.uint16:
value = 65535
elif dtype.kind == 'f':
value = 1
else:
value = 255
if value == 'auto':
if (self._bandRanges.get(frame) and
numpy.all(self._bandRanges[frame]['min'] >= 0) and
Expand Down Expand Up @@ -1102,11 +1122,15 @@ def _applyStyle(self, image, style, x, y, z, frame=None): # noqa
:param frame: the frame to use for auto ranging.
:returns: a styled image.
"""
dtype = style.get('dtype')
axis = style.get('axis')
style = style['bands'] if 'bands' in style else [style]
output = numpy.zeros((image.shape[0], image.shape[1], 4), float)
mainImage = image
mainFrame = frame
for entry in style:
dtype = dtype if dtype is not None else entry.get('dtype')
axis = axis if axis is not None else entry.get('axis')
bandidx = 0 if image.shape[2] <= 2 else 1
band = None
if ((entry.get('frame') is None and not entry.get('framedelta')) or
Expand Down Expand Up @@ -1194,6 +1218,12 @@ def _applyStyle(self, image, style, x, y, z, frame=None): # noqa
else:
output[:, :, channel] = numpy.maximum(
output[:, :, channel], numpy.where(keep, clrs, 0))
if dtype == 'uint16':
output = (output * 65535 / 255).astype(numpy.uint16)
elif dtype == 'float':
output /= 255
if axis is not None and 0 <= int(axis) < output.shape[2]:
output = output[:, :, axis:axis + 1]
return output

def _outputTileNumpyStyle(self, tile, applyStyle, x, y, z, frame=None):
Expand Down
16 changes: 16 additions & 0 deletions test/test_source_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,22 @@ def testStyleMinMaxThreshold():
assert numpy.any(image != imageB)
assert image[0][0][0] == 252
assert imageB[0][0][0] == 254
sourceC = large_image_source_tiff.open(
imagePath, style=json.dumps({'min': 'full', 'max': 'full'}))
imageC, _ = sourceC.getRegion(
output={'maxWidth': 256, 'maxHeight': 256}, format=constants.TILE_FORMAT_NUMPY)
assert numpy.any(image != imageC)
assert imageC[0][0][0] == 253


def testStyleDtypeAxis():
imagePath = datastore.fetch('sample_image.ptif')
source = large_image_source_tiff.open(
imagePath, style=json.dumps({'dtype': 'uint16', 'axis': 1}))
image, _ = source.getRegion(
output={'maxWidth': 456, 'maxHeight': 96}, format=constants.TILE_FORMAT_NUMPY)
assert image.shape[2] == 1
assert image[0][0][0] == 65021


def testStyleNoData():
Expand Down

0 comments on commit 959f50d

Please sign in to comment.