bilinearInterpolator.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. # import csv
  2. import math
  3. import numpy as np
  4. class bilinearInterpolator:
  5. """
  6. This class takes a collection of 3-dimensional points from a .csv file.
  7. It contains a bilinear interpolator to find unknown points within the grid.
  8. """
  9. @property
  10. def probedGrid(self):
  11. return self._probedGrid
  12. """
  13. Constructor takes a file with a .csv extension and creates an evenly-spaced 'ideal' grid from the data points.
  14. This is done to get around any floating point errors that may exist in the data
  15. """
  16. def __init__(self, pointsFile):
  17. self.pointsFile = pointsFile
  18. self.points = np.loadtxt(self.pointsFile, delimiter=',')
  19. self.xMin, self.xMax, self.xSpacing, self.xCount = self._axisParams(0)
  20. self.yMin, self.yMax, self.ySpacing, self.yCount = self._axisParams(1)
  21. # generate ideal grid to match actually probed points -- this is due to floating-point error issues
  22. idealGrid = ([
  23. [(x, y) for x in np.linspace(self.xMin, self.xMax, self.xCount, True)]
  24. for y in np.linspace(self.yMin, self.yMax, self.yCount, True)
  25. ])
  26. self._probedGrid = [[0] * self.yCount for i in range(0, self.xCount)]
  27. # align ideal grid indices with probed data points
  28. for rowIndex, row in enumerate(idealGrid):
  29. for colIndex, idealPoint in enumerate(row):
  30. minSqDist = math.inf
  31. for probed in self.points:
  32. # find closest point in ideal grid that corresponds to actual tested point
  33. # put z value in correct index
  34. sqDist = pow(probed[0] - idealPoint[0], 2) + pow(probed[1] - idealPoint[1], 2)
  35. if sqDist <= minSqDist:
  36. minSqDist = sqDist
  37. indexX = rowIndex
  38. indexY = colIndex
  39. closestProbed = probed
  40. self.probedGrid[indexY][indexX] = closestProbed
  41. def Interpolate(self, point):
  42. """
  43. Bilinear interpolation method to determine unknown z-values within grid of known z-values.
  44. NOTE: If one axis is outside the grid, linear interpolation is used instead.
  45. If both axes are outside of the grid, the z-value of the closest corner of the grid is returned.
  46. """
  47. lin = False
  48. if point[0] < self.xMin:
  49. ix1 = 0
  50. lin = True
  51. elif point[0] > self.xMax:
  52. ix1 = self.xCount-1
  53. lin = True
  54. else:
  55. ix1 = math.floor((point[0] - self.xMin)/self.xSpacing)
  56. ix2 = math.ceil((point[0] - self.xMin)/self.xSpacing)
  57. def interpolatePoint(p1, p2, pt, axis):
  58. return (p2[2]*(pt[axis] - p1[axis]) + p1[2]*(p2[axis] - pt[axis]))/(p2[axis] - p1[axis])
  59. if point[1] < self.yMin:
  60. if lin:
  61. return self.probedGrid[ix1][0][2]
  62. return interpolatePoint(self.probedGrid[ix1][0], self.probedGrid[ix2][0], point, 0)
  63. elif point[1] > self.yMax:
  64. if lin:
  65. return self.probedGrid[ix1][self.yCount - 1][2]
  66. return interpolatePoint(
  67. self.probedGrid[ix1][self.yCount - 1], self.probedGrid[ix2][self.yCount - 1], point, 0)
  68. else:
  69. iy1 = math.floor((point[1] - self.yMin)/self.ySpacing)
  70. iy2 = math.ceil((point[1] - self.yMin)/self.ySpacing)
  71. # if x was at an extrema, but y was not, perform linear interpolation on x axis
  72. if lin:
  73. return interpolatePoint(self.probedGrid[ix1][iy1], self.probedGrid[ix1][iy2], point, 1)
  74. def specialDiv(a, b):
  75. if b == 0:
  76. return 0.5
  77. else:
  78. return a/b
  79. x1 = self.probedGrid[ix1][iy1][0]
  80. x2 = self.probedGrid[ix2][iy1][0]
  81. y1 = self.probedGrid[ix2][iy1][1]
  82. y2 = self.probedGrid[ix2][iy2][1]
  83. Q11 = self.probedGrid[ix1][iy1][2]
  84. Q12 = self.probedGrid[ix1][iy2][2]
  85. Q21 = self.probedGrid[ix2][iy1][2]
  86. Q22 = self.probedGrid[ix2][iy2][2]
  87. r1 = specialDiv(point[0]-x1, x2-x1)*Q21 + specialDiv(x2-point[0], x2-x1)*Q11
  88. r2 = specialDiv(point[0]-x1, x2-x1)*Q22 + specialDiv(x2-point[0], x2-x1)*Q12
  89. p = specialDiv(point[1]-y1, y2-y1)*r2 + specialDiv(y2-point[1], y2-y1)*r1
  90. return p
  91. # Returns the min, max, spacing and size of one axis of the 2D grid
  92. def _axisParams(self, sortAxis):
  93. # sort the set and eliminate the previous, unsorted set
  94. srtSet = sorted(self.points, key=lambda x: x[sortAxis])
  95. dists = []
  96. for item0, item1 in zip(srtSet[:(len(srtSet)-2)], srtSet[1:]):
  97. dists.append(float(item1[sortAxis]) - float(item0[sortAxis]))
  98. axisSpacing = max(dists)
  99. # add an extra one for axisCount to account for the starting point
  100. axisMin = float(min(srtSet, key=lambda x: x[sortAxis])[sortAxis])
  101. axisMax = float(max(srtSet, key=lambda x: x[sortAxis])[sortAxis])
  102. axisRange = axisMax - axisMin
  103. axisCount = round((axisRange/axisSpacing) + 1)
  104. return axisMin, axisMax, axisSpacing, axisCount