Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.2 KiB

  1. This blog post is the first part of a multi-post series on using quadtrees in Python.
  2. This post goes over quadtrees' basics and how you can implement a basic point quadtree in Python.
  3. Future posts aim to apply quadtrees in image segmentation and analysis.
  4. A quadtree is a data structure where each node has exactly four children. This property makes it particularly suitable for spatial searching.
  5. In a point-quadtree, leaf nodes are a single unit of spatial information. A quadtree is constructed by continuously dividing each node until each leaf node only has a single node inside of it.
  6. However, this partitioning can be modified so that each leaf node only contains at most K elements or that each cell can be at a maximum X large.
  7. Although usually used in two-dimensions, quadtrees can be expanded to an arbitrary amount of dimensions. The lovely property of quadtrees is that it is a "dimensional reduction" algorithm. Rather than operating in O(n^2) for a traditional linear search in two dimensions, a quadtree can accomplish close to O(log n) time for most operations.
  8. # Implementing a Point Quadtree
  9. To implement a quadtree, we only need a few pieces. First, we need some way to represent our spacial information.
  10. In this application, we are only using points; however, we may choose to associate data with each point for an application.
  11. ```python
  12. class Point():
  13. def __init__(self, x, y):
  14. self.x = x
  15. self.y = y
  16. ```
  17. The second thing that we need is a tree representation.
  18. Like all tree nodes, it has children; however, what is unique about a quadtree is that each node represents a geometric region.
  19. This geometric region has a shape represented by a location and a width and height. Additionally, if this is a leaf node, we need to have our node store the region's points.
  20. ```python
  21. class Node():
  22. def __init__(self, x0, y0, w, h, points):
  23. self.x0 = x0
  24. self.y0 = y0
  25. self.width = w
  26. self.height = h
  27. self.points = points
  28. self.children = []
  29. def get_width(self):
  30. return self.width
  31. def get_height(self):
  32. return self.height
  33. def get_points(self):
  34. return self.points
  35. ```
  36. To generate the quadtree, we will be taking a top-down approach were we recursively divide the node into four regions until a certain threshold has been satisfied.
  37. In this case, we are stopping division when each node contains less than k nodes.
  38. ```python
  39. def recursive_subdivide(node, k):
  40. if len(node.points)<=k:
  41. return
  42. w_ = float(node.width/2)
  43. h_ = float(node.height/2)
  44. p = contains(node.x0, node.y0, w_, h_, node.points)
  45. x1 = Node(node.x0, node.y0, w_, h_, p)
  46. recursive_subdivide(x1, k)
  47. p = contains(node.x0, node.y0+h_, w_, h_, node.points)
  48. x2 = Node(node.x0, node.y0+h_, w_, h_, p)
  49. recursive_subdivide(x2, k)
  50. p = contains(node.x0+w_, node.y0, w_, h_, node.points)
  51. x3 = Node(node.x0 + w_, node.y0, w_, h_, p)
  52. recursive_subdivide(x3, k)
  53. p = contains(node.x0+w_, node.y0+h_, w_, h_, node.points)
  54. x4 = Node(node.x0+w_, node.y0+h_, w_, h_, p)
  55. recursive_subdivide(x4, k)
  56. node.children = [x1, x2, x3, x4]
  57. def contains(x, y, w, h, points):
  58. pts = []
  59. for point in points:
  60. if point.x >= x and point.x <= x+w and point.y>=y and point.y<=y+h:
  61. pts.append(point)
  62. return pts
  63. def find_children(node):
  64. if not node.children:
  65. return [node]
  66. else:
  67. children = []
  68. for child in node.children:
  69. children += (find_children(child))
  70. return children
  71. ```
  72. The QTree class is used to tie together all the data associated with creating a quadtree.
  73. This class is also used to generate dummy data and graph it using matplotlib.
  74. ```python
  75. import random
  76. import matplotlib.pyplot as plt # plotting libraries
  77. import matplotlib.patches as patches
  78. class QTree():
  79. def __init__(self, k, n):
  80. self.threshold = k
  81. self.points = [Point(random.uniform(0, 10), random.uniform(0, 10)) for x in range(n)]
  82. self.root = Node(0, 0, 10, 10, self.points)
  83. def add_point(self, x, y):
  84. self.points.append(Point(x, y))
  85. def get_points(self):
  86. return self.points
  87. def subdivide(self):
  88. recursive_subdivide(self.root, self.threshold)
  89. def graph(self):
  90. fig = plt.figure(figsize=(12, 8))
  91. plt.title("Quadtree")
  92. c = find_children(self.root)
  93. print("Number of segments: %d" %len(c))
  94. areas = set()
  95. for el in c:
  96. areas.add(el.width*el.height)
  97. print("Minimum segment area: %.3f units" %min(areas))
  98. for n in c:
  99. plt.gcf().gca().add_patch(patches.Rectangle((n.x0, n.y0), n.width, n.height, fill=False))
  100. x = [point.x for point in self.points]
  101. y = [point.y for point in self.points]
  102. plt.plot(x, y, 'ro') # plots the points as red dots
  103. plt.show()
  104. return
  105. ```
  106. Creating a quadtree where each cell can only contain at the most section will produce a lot of cells.
  107. ![png](media/quad-tree/output_4_1.png)
  108. If we change the hyperparameter to split until there is at most two objects per cell, we get larger cells.
  109. ![png](media/quad-tree/output_5_1.png)
  110. # Future Work
  111. In the near future, I plan on making a post on how you can use quadtrees to do image compression.