Arrange.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import numpy as np
  2. ## Some polygon converted to an array
  3. class ShapeArray:
  4. def __init__(self, arr, offset_x, offset_y, scale = 1):
  5. self.arr = arr
  6. self.offset_x = offset_x
  7. self.offset_y = offset_y
  8. self.scale = scale
  9. @classmethod
  10. def from_polygon(cls, vertices, scale = 1):
  11. # scale
  12. vertices = vertices * scale
  13. # offset
  14. offset_y = int(np.amin(vertices[:, 0]))
  15. offset_x = int(np.amin(vertices[:, 1]))
  16. # normalize to 0
  17. vertices[:, 0] = np.add(vertices[:, 0], -offset_y)
  18. vertices[:, 1] = np.add(vertices[:, 1], -offset_x)
  19. shape = [int(np.amax(vertices[:, 0])), int(np.amax(vertices[:, 1]))]
  20. arr = cls.array_from_polygon(shape, vertices)
  21. return cls(arr, offset_x, offset_y)
  22. ## Return indices that mark one side of the line, used by array_from_polygon
  23. # Uses the line defined by p1 and p2 to check array of
  24. # input indices against interpolated value
  25. # Returns boolean array, with True inside and False outside of shape
  26. # Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array
  27. @classmethod
  28. def _check(cls, p1, p2, base_array):
  29. """
  30. """
  31. if p1[0] == p2[0] and p1[1] == p2[1]:
  32. return
  33. idxs = np.indices(base_array.shape) # Create 3D array of indices
  34. p1 = p1.astype(float)
  35. p2 = p2.astype(float)
  36. if p2[0] == p1[0]:
  37. sign = np.sign(p2[1] - p1[1])
  38. return idxs[1] * sign
  39. if p2[1] == p1[1]:
  40. sign = np.sign(p2[0] - p1[0])
  41. return idxs[1] * sign
  42. # Calculate max column idx for each row idx based on interpolated line between two points
  43. max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
  44. sign = np.sign(p2[0] - p1[0])
  45. return idxs[1] * sign <= max_col_idx * sign
  46. @classmethod
  47. def array_from_polygon(cls, shape, vertices):
  48. """
  49. Creates np.array with dimensions defined by shape
  50. Fills polygon defined by vertices with ones, all other values zero
  51. Only works correctly for convex hull vertices
  52. """
  53. base_array = np.zeros(shape, dtype=float) # Initialize your array of zeros
  54. fill = np.ones(base_array.shape) * True # Initialize boolean array defining shape fill
  55. # Create check array for each edge segment, combine into fill array
  56. for k in range(vertices.shape[0]):
  57. fill = np.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
  58. # Set all values inside polygon to one
  59. base_array[fill] = 1
  60. return base_array
  61. class Arrange:
  62. def __init__(self, x, y, offset_x, offset_y, scale=1):
  63. self.shape = (y, x)
  64. self._priority = np.zeros((x, y), dtype=np.int32)
  65. self._occupied = np.zeros((x, y), dtype=np.int32)
  66. self._scale = scale # convert input coordinates to arrange coordinates
  67. self._offset_x = offset_x
  68. self._offset_y = offset_y
  69. ## Fill priority, take offset as center. lower is better
  70. def centerFirst(self):
  71. self._priority = np.fromfunction(
  72. lambda i, j: abs(self._offset_x-i)+abs(self._offset_y-j), self.shape)
  73. ## Return the amount of "penalty points" for polygon, which is the sum of priority
  74. # 999999 if occupied
  75. def check_shape(self, x, y, shape_arr):
  76. x = int(self._scale * x)
  77. y = int(self._scale * y)
  78. offset_x = x + self._offset_x + shape_arr.offset_x
  79. offset_y = y + self._offset_y + shape_arr.offset_y
  80. occupied_slice = self._occupied[
  81. offset_y:offset_y + shape_arr.arr.shape[0],
  82. offset_x:offset_x + shape_arr.arr.shape[1]]
  83. if np.any(occupied_slice[np.where(shape_arr.arr == 1)]):
  84. return 999999
  85. prio_slice = self._priority[
  86. offset_y:offset_y + shape_arr.arr.shape[0],
  87. offset_x:offset_x + shape_arr.arr.shape[1]]
  88. return np.sum(prio_slice[np.where(shape_arr.arr == 1)])
  89. ## Slower but better (it tries all possible locations)
  90. def bestSpot2(self, shape_arr):
  91. best_x, best_y, best_points = None, None, None
  92. min_y = max(-shape_arr.offset_y, 0) - self._offset_y
  93. max_y = self.shape[0] - shape_arr.arr.shape[0] - self._offset_y
  94. min_x = max(-shape_arr.offset_x, 0) - self._offset_x
  95. max_x = self.shape[1] - shape_arr.arr.shape[1] - self._offset_x
  96. for y in range(min_y, max_y):
  97. for x in range(min_x, max_x):
  98. penalty_points = self.check_shape(x, y, shape_arr)
  99. if best_points is None or penalty_points < best_points:
  100. best_points = penalty_points
  101. best_x, best_y = x, y
  102. return best_x, best_y, best_points
  103. ## Faster
  104. def bestSpot(self, shape_arr):
  105. min_y = max(-shape_arr.offset_y, 0) - self._offset_y
  106. max_y = self.shape[0] - shape_arr.arr.shape[0] - self._offset_y
  107. min_x = max(-shape_arr.offset_x, 0) - self._offset_x
  108. max_x = self.shape[1] - shape_arr.arr.shape[1] - self._offset_x
  109. for prio in range(200):
  110. tryout_idx = np.where(self._priority == prio)
  111. for idx in range(len(tryout_idx[0])):
  112. x = tryout_idx[0][idx]
  113. y = tryout_idx[1][idx]
  114. projected_x = x - self._offset_x
  115. projected_y = y - self._offset_y
  116. if projected_x < min_x or projected_x > max_x or projected_y < min_y or projected_y > max_y:
  117. continue
  118. # array to "world" coordinates
  119. penalty_points = self.check_shape(projected_x, projected_y, shape_arr)
  120. if penalty_points != 999999:
  121. return projected_x, projected_y, penalty_points
  122. return None, None, None # No suitable location found :-(
  123. def place(self, x, y, shape_arr):
  124. x = int(self._scale * x)
  125. y = int(self._scale * y)
  126. offset_x = x + self._offset_x + shape_arr.offset_x
  127. offset_y = y + self._offset_y + shape_arr.offset_y
  128. occupied_slice = self._occupied[
  129. offset_y:offset_y + shape_arr.arr.shape[0],
  130. offset_x:offset_x + shape_arr.arr.shape[1]]
  131. occupied_slice[np.where(shape_arr.arr == 1)] = 1