Source code for improver.utilities.rescale

# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown copyright. The Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Provides support utility for rescaling data."""

from typing import Callable, List, Optional, Tuple, Union

import numpy as np
from iris.cube import Cube
from numpy import ndarray


[docs]def rescale( data: ndarray, data_range: Optional[Union[Tuple[float, float], List[float]]] = None, scale_range: Union[Tuple[float, float], List[float]] = (0.0, 1.0), clip: bool = False, ) -> ndarray: """ Rescale data array so that data_min => scale_min and data_max => scale max. All adjustments are linear Args: data: Source values data_range: List containing two floats Lowest and highest source value to rescale. Default value of None is converted to [min(data), max(data)] scale_range: List containing two floats Lowest and highest value after rescaling. Defaults to (0., 1.) clip: If True, points where data were outside the scaling range will be set to the scale min or max appropriately. Default is False which continues the scaling beyond min and max. Returns: Output array of scaled data. Has same shape as data. """ data_left = np.min(data) if data_range is None else data_range[0] data_right = np.max(data) if data_range is None else data_range[1] scale_left = scale_range[0] scale_right = scale_range[1] # Range check if data_left == data_right: raise ValueError( "Cannot rescale a zero input range ({} -> {})".format(data_left, data_right) ) if scale_left == scale_right: raise ValueError( "Cannot rescale a zero output range ({} -> {})".format( scale_left, scale_right ) ) result = ( (data - data_left) * (scale_right - scale_left) / (data_right - data_left) ) + scale_left if clip: result = np.clip( result, min(scale_left, scale_right), max(scale_left, scale_right) ) return result
[docs]def apply_double_scaling( data_cube: Cube, scaled_cube: Cube, data_vals: Tuple[float, float, float], scaling_vals: Tuple[float, float, float], combine_function: Callable[[ndarray, ndarray], ndarray] = np.minimum, ) -> ndarray: """ From data_cube, an array of limiting values is created based on a linear rescaling from three data_vals to three scaling_vals. The three values refer to a lower-bound, a mid-point and an upper-bound. This rescaled data_cube is combined with scaled_cube to produce an array containing either the higher or lower value as needed. Args: data_cube: Data from which to create a rescaled data array scaled_cube: Data already in the rescaled frame of reference which will be combined with the rescaled data_cube using the combine_function. data_vals: Lower, mid and upper points to rescale data_cube from scaling_vals: Lower, mid and upper points to rescale data_cube to combine_function: Function that takes two arrays of the same shape and returns one array of the same shape. Expected to be numpy.minimum (default) or numpy.maximum. Returns: Output data from data_cube after rescaling and combining with scaled_cube. This array will have the same dimensions as scaled_cube. """ # Where data are below the specified mid-point (data_vals[1]): # Set rescaled_data to be a rescaled value between the first and mid-point # Elsewhere # Set rescaled_data to be a rescaled value between the mid- and last point rescaled_data = np.where( data_cube.data < data_vals[1], rescale( data_cube.data, data_range=(data_vals[0], data_vals[1]), scale_range=(scaling_vals[0], scaling_vals[1]), clip=True, ), rescale( data_cube.data, data_range=(data_vals[1], data_vals[2]), scale_range=(scaling_vals[1], scaling_vals[2]), clip=True, ), ) # Ensure scaled_cube is no larger or smaller than the rescaled_data: return combine_function(scaled_cube.data, rescaled_data)