141 lines
5.2 KiB
Python
141 lines
5.2 KiB
Python
#############################################################################
|
|
# Copyright (c) 2013 by Panagiotis Mavrogiorgos
|
|
# 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(s) of the copyright holders 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 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 HOLDERS 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.
|
|
#############################################################################
|
|
#
|
|
# @license: http://opensource.org/licenses/BSD-3-Clause
|
|
|
|
from bisect import bisect_left
|
|
import logging
|
|
|
|
log = logging.getLogger('base')
|
|
|
|
|
|
class BilinearInterpolation(object):
|
|
"""
|
|
Bilinear interpolation with optional extrapolation.
|
|
Usage:
|
|
table = BilinearInterpolation(
|
|
x_index=(1, 2, 3),
|
|
y_index=(1, 2, 3),
|
|
values=((110, 120, 130),
|
|
(210, 220, 230),
|
|
(310, 320, 330)),
|
|
extrapolate=True)
|
|
|
|
assert table(1, 1) == 110
|
|
assert table(2.5, 2.5) == 275
|
|
|
|
"""
|
|
def __init__(self, x_index, y_index, values):
|
|
# sanity check
|
|
x_length = len(x_index)
|
|
y_length = len(y_index)
|
|
|
|
if x_length < 2 or y_length < 2:
|
|
raise ValueError("Table must be at least 2x2.")
|
|
if y_length != len(values):
|
|
raise ValueError("Table must have equal number of rows to y_index.")
|
|
if any(x2 - x1 <= 0 for x1, x2 in zip(x_index, x_index[1:])):
|
|
raise ValueError("x_index must be in strictly ascending order!")
|
|
if any(y2 - y1 <= 0 for y1, y2 in zip(y_index, y_index[1:])):
|
|
raise ValueError("y_index must be in strictly ascending order!")
|
|
|
|
self.x_index = x_index
|
|
self.y_index = y_index
|
|
self.values = values
|
|
self.x_length = x_length
|
|
self.y_length = y_length
|
|
|
|
def __call__(self, x, y):
|
|
# local lookups
|
|
x_index, y_index, values = self.x_index, self.y_index, self.values
|
|
|
|
i = bisect_left(x_index, x) - 1
|
|
j = bisect_left(y_index, y) - 1
|
|
|
|
# fix x index
|
|
if i == -1:
|
|
x_slice = slice(None, 2)
|
|
elif i == self.x_length - 1:
|
|
x_slice = slice(-2, None)
|
|
else:
|
|
x_slice = slice(i, i + 2)
|
|
|
|
# fix y index
|
|
if j == -1:
|
|
j = 0
|
|
y_slice = slice(None, 2)
|
|
elif j == self.y_length - 1:
|
|
j = -2
|
|
y_slice = slice(-2, None)
|
|
else:
|
|
y_slice = slice(j, j + 2)
|
|
|
|
# if the extrapolations is False this will fail
|
|
x1, x2 = x_index[x_slice]
|
|
y1, y2 = y_index[y_slice]
|
|
z11, z12 = values[j][x_slice]
|
|
z21, z22 = values[j + 1][x_slice]
|
|
|
|
return (z11 * (x2 - x) * (y2 - y) +
|
|
z21 * (x - x1) * (y2 - y) +
|
|
z12 * (x2 - x) * (y - y1) +
|
|
z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1))
|
|
|
|
|
|
def bilinear_interpolation(x, y, points):
|
|
"""
|
|
https://stackoverflow.com/questions/8661537/how-to-perform-bilinear-interpolation-in-python
|
|
|
|
Interpolate (x,y) from values associated with four points.
|
|
|
|
The four points are a list of four triplets: (x, y, value).
|
|
The four points can be in any order. They should form a rectangle.
|
|
|
|
>>> bilinear_interpolation(12, 5.5,
|
|
... [(10, 4, 100),
|
|
... (20, 4, 200),
|
|
... (10, 6, 150),
|
|
... (20, 6, 300)])
|
|
165.0
|
|
"""
|
|
# See formula at: http://en.wikipedia.org/wiki/Bilinear_interpolation
|
|
|
|
points = sorted(points) # order points by x, then by y
|
|
(x1, y1, q11), (_x1, y2, q12), (x2, _y1, q21), (_x2, _y2, q22) = points
|
|
|
|
if x1 != _x1 or x2 != _x2 or y1 != _y1 or y2 != _y2:
|
|
raise ValueError('points do not form a rectangle')
|
|
if not x1 <= x <= x2 or not y1 <= y <= y2:
|
|
raise ValueError('(x, y) not within the rectangle')
|
|
|
|
return (q11 * (x2 - x) * (y2 - y) +
|
|
q21 * (x - x1) * (y2 - y) +
|
|
q12 * (x2 - x) * (y - y1) +
|
|
q22 * (x - x1) * (y - y1)
|
|
) / ((x2 - x1) * (y2 - y1) + 0.0)
|