Heatmap_Multiple.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import UnityEngine as ue
  2. import numpy as np
  3. import seaborn as sns
  4. import pandas as pd
  5. import matplotlib.pyplot as plt
  6. import matplotlib.patches as patches
  7. from matplotlib.colors import LinearSegmentedColormap
  8. from matplotlib.tri import Triangulation
  9. WIDTH = int(70)
  10. HEIGHT = int(35)
  11. OBSTACLE_PATH1 = "Assets/Data_image/obstacle1.pkl"
  12. OBSTACLE_PATH2 = "Assets/Data_image/obstacle2.pkl"
  13. OBSTACLE_PATH3 = "Assets/Data_image/obstacle3.pkl"
  14. POSITION_PATH1 = ue.Application.dataPath + '/Data_position/Walk1.csv'
  15. POSITION_PATH2 = ue.Application.dataPath + '/Data_position/Walk2.csv'
  16. POSITION_PATH3 = ue.Application.dataPath + '/Data_position/Walk3.csv'
  17. HEATMAP_PATH = "Assets/Data_image/heatmapMultiple1.png"
  18. # Generate only if obstacles change
  19. def set_obstacles(maximum, path):
  20. """
  21. set_obstacles creates a DataFrame which marks the positions of the obstacles.
  22. The obstacles are marked with the negative maximum and the file is placed at the path.
  23. The negative maximum is used so that the cmap can assign the correct colors to the values.
  24. Since no other negative numbers exist and the next highest number is 0, the negative values are painted 'grey'
  25. and the other [0-maximum] are painted according to the color gradient between the two specified colors.
  26. :param maximum: Is the maximum to the associated positions file
  27. :param path: Is the location of the file created in this function
  28. """
  29. global list_obstacle_length
  30. positions = pd.DataFrame(np.zeros((HEIGHT, WIDTH)))
  31. obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
  32. for obstacle in obstacles:
  33. if(obstacle.layer == 15 or obstacle.layer == 12):
  34. startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
  35. endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
  36. startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
  37. endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
  38. for currentW in range(startWidth, endWidth, 1):
  39. for currentH in range(startHeight, endHeight, 1):
  40. positions[currentW][currentH] = -maximum
  41. positions.to_pickle(path)
  42. def set_patches(plt):
  43. """
  44. set_patches uses the positions of the obstacles to draw a black border around them.
  45. :param plt: The plot, where the figure is placed, so it can be modified by this function
  46. """
  47. obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
  48. for obstacle in obstacles:
  49. if(obstacle.layer == 15):
  50. startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
  51. endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
  52. startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
  53. endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
  54. # plt.gca().add_patch(
  55. plt.add_patch(
  56. patches.Rectangle(
  57. (startWidth, startHeight),
  58. endWidth - startWidth,
  59. endHeight - startHeight,
  60. fill=False,
  61. color='black'
  62. )
  63. )
  64. def triangulation_for_triheatmap(M, N):
  65. """
  66. triangulation_for_triheatmap divides each pixel of the heatmap into 4 triangles of equal size.
  67. For this it first determines all x and y coordinates of the vertices of all triangles.
  68. Then it creates the triangles based on the x and y coordinates.
  69. :param M: The width of the Heatmap
  70. :param N: The height of the Heatmap
  71. :return: The created triangles
  72. """
  73. xv, yv = np.meshgrid(np.arange(0, M+1), np.arange(0, N+1)) # vertices of the little squares
  74. xc, yc = np.meshgrid(np.arange(0.5, M), np.arange(0.5, N)) # centers of the little squares
  75. x = np.concatenate([xv.ravel(), xc.ravel()])
  76. y = np.concatenate([yv.ravel(), yc.ravel()])
  77. cstart = (M + 1) * (N + 1) # indices of the centers
  78. trianglesN = [(i + 1 + (j + 1) * (M + 1), i + (j + 1) * (M + 1), cstart + i + j * M)
  79. for j in range(N) for i in range(M)]
  80. trianglesE = [(i + 1 + j * (M + 1), i + 1 + (j + 1) * (M + 1), cstart + i + j * M)
  81. for j in range(N) for i in range(M)]
  82. trianglesS = [(i + j * (M + 1), i + 1 + j * (M + 1), cstart + i + j * M)
  83. for j in range(N) for i in range(M)]
  84. trianglesW = [(i + (j + 1) * (M + 1), i + j * (M + 1), cstart + i + j * M)
  85. for j in range(N) for i in range(M)]
  86. return [Triangulation(x, y, triangles) for triangles in [trianglesN, trianglesE, trianglesS, trianglesW]]
  87. # 1. Get position data from csv file
  88. data1 = pd.read_csv(POSITION_PATH1, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
  89. data1 = data1.round(0)
  90. data2 = pd.read_csv(POSITION_PATH2, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
  91. data2 = data2.round(0)
  92. data3 = pd.read_csv(POSITION_PATH3, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
  93. data3 = data3.round(0)
  94. # 2. Group by positions and count appearance
  95. data_count1 = data1.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
  96. data_count2 = data2.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
  97. data_count3 = data3.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
  98. # 3. Create wide-form DataFrame for generating heatmap
  99. positions1 = data_count1.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
  100. positions2 = data_count2.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
  101. positions3 = data_count3.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
  102. # 4. Fill missing values
  103. positions1.fillna(0, inplace=True)
  104. positions2.fillna(0, inplace=True)
  105. positions3.fillna(0, inplace=True)
  106. # 5. Reindex DataFrame (70,35) size of Surface; (70, 35) first x width then z height
  107. positions1 = positions1.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
  108. positions1 = positions1.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
  109. positions2 = positions2.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
  110. positions2 = positions2.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
  111. positions3 = positions3.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
  112. positions3 = positions3.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
  113. # 6.1 Get maximum value of positions data
  114. max1 = pd.DataFrame(positions1).max().max() # Gets the maximum of each column and than the maximum of the maximums
  115. max2 = pd.DataFrame(positions2).max().max() # Gets the maximum of each column and than the maximum of the maximums
  116. max3 = pd.DataFrame(positions3).max().max() # Gets the maximum of each column and than the maximum of the maximums
  117. # 6.2 Get obstacles (obstacles, market stalls) and paste specific value in positions
  118. # Therefore save pkl file in folder and read from it afterwards
  119. set_obstacles(max1, OBSTACLE_PATH1)
  120. set_obstacles(max2, OBSTACLE_PATH2)
  121. set_obstacles(max3, OBSTACLE_PATH3)
  122. positions_obstacles = pd.read_pickle(OBSTACLE_PATH1)
  123. positions_heatmap1 = pd.read_pickle(OBSTACLE_PATH1)
  124. positions_heatmap2 = pd.read_pickle(OBSTACLE_PATH2)
  125. positions_heatmap3 = pd.read_pickle(OBSTACLE_PATH3)
  126. # 7 Merge positions data with obstacles data
  127. positions_heatmap1.where(positions_heatmap1 != 0, positions1, inplace=True)
  128. positions_heatmap2.where(positions_heatmap2 != 0, positions2, inplace=True)
  129. positions_heatmap3.where(positions_heatmap3 != 0, positions3, inplace=True)
  130. # 7.1 Debug Output
  131. # positions_heatmap1 = pd.DataFrame(positions_heatmap1)
  132. # positions_heatmap1.to_html('Assets/Data_image/positions_heatmap1.html')
  133. # positions_heatmap2 = pd.DataFrame(positions_heatmap2)
  134. # positions_heatmap2.to_html('Assets/Data_image/positions_heatmap2.html')
  135. # positions_heatmap3 = pd.DataFrame(positions_heatmap3)
  136. # positions_heatmap3.to_html('Assets/Data_image/positions_heatmap3.html')
  137. # 8.1 Divide the pixel in four equal triangles and plot heatmap
  138. values = [positions_heatmap1, positions_heatmap2, positions_obstacles, positions_heatmap3]
  139. triangul = triangulation_for_triheatmap(WIDTH, HEIGHT)
  140. cmaps = [LinearSegmentedColormap.from_list(name='2019', colors=['grey', (0.40,0.76,0.65), (0.11,0.62,0.47)]),
  141. LinearSegmentedColormap.from_list(name='2020', colors=['grey', (0.99,0.55,0.38), (0.85,0.37,0.01)]),
  142. LinearSegmentedColormap.from_list(name='2021', colors=['grey', (1, 1, 0.975)]),
  143. LinearSegmentedColormap.from_list(name='2021', colors=['grey', (0.55,0.63,0.80), (0.46,0.44,0.70)])]
  144. fig, ax = plt.subplots()
  145. imgs = [ax.tripcolor(t, np.ravel(val), cmap=cmap)
  146. for t, val, cmap in zip(triangul, values, cmaps)]
  147. # 8.2 Mark the Market stalls
  148. set_patches(ax)
  149. # 9.1 Set visual settings
  150. ax.tick_params(axis='both', which='both', bottom='off', labelbottom='off', left='off', labelleft='off')
  151. ax.margins(x=0, y=0)
  152. ax.set_aspect('equal', 'box') # square cells
  153. plt.tight_layout()
  154. # 9.2 Show Heatmap
  155. plt.show()
  156. # 10. Save Heatmap
  157. fig.savefig(HEATMAP_PATH, transparent=True)