123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import UnityEngine as ue
- import numpy as np
- import seaborn as sns
- import pandas as pd
- import matplotlib.pyplot as plt
- import matplotlib.patches as patches
- from matplotlib.colors import LinearSegmentedColormap
- from matplotlib.tri import Triangulation
- WIDTH = int(70)
- HEIGHT = int(35)
- OBSTACLE_PATH1 = "Assets/Data_image/obstacle1.pkl"
- OBSTACLE_PATH2 = "Assets/Data_image/obstacle2.pkl"
- OBSTACLE_PATH3 = "Assets/Data_image/obstacle3.pkl"
- POSITION_PATH1 = ue.Application.dataPath + '/Data_position/Walk1.csv'
- POSITION_PATH2 = ue.Application.dataPath + '/Data_position/Walk2.csv'
- POSITION_PATH3 = ue.Application.dataPath + '/Data_position/Walk3.csv'
- HEATMAP_PATH = "Assets/Data_image/heatmapMultiple1.png"
- # Generate only if obstacles change
- def set_obstacles(maximum, path):
- """
- set_obstacles creates a DataFrame which marks the positions of the obstacles.
- The obstacles are marked with the negative maximum and the file is placed at the path.
- The negative maximum is used so that the cmap can assign the correct colors to the values.
- Since no other negative numbers exist and the next highest number is 0, the negative values are painted 'grey'
- and the other [0-maximum] are painted according to the color gradient between the two specified colors.
-
- :param maximum: Is the maximum to the associated positions file
- :param path: Is the location of the file created in this function
- """
- global list_obstacle_length
- positions = pd.DataFrame(np.zeros((HEIGHT, WIDTH)))
- obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
- for obstacle in obstacles:
- if(obstacle.layer == 15 or obstacle.layer == 12):
- startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
- endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
- startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
- endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
- for currentW in range(startWidth, endWidth, 1):
- for currentH in range(startHeight, endHeight, 1):
- positions[currentW][currentH] = -maximum
- positions.to_pickle(path)
- def set_patches(plt):
- """
- set_patches uses the positions of the obstacles to draw a black border around them.
-
- :param plt: The plot, where the figure is placed, so it can be modified by this function
- """
- obstacles = ue.Object.FindObjectsOfType(ue.GameObject)
- for obstacle in obstacles:
- if(obstacle.layer == 15):
- startWidth = int(obstacle.transform.position.x - obstacle.transform.localScale.x / 2)
- endWidth = int(obstacle.transform.position.x + obstacle.transform.localScale.x / 2)
- startHeight = int(obstacle.transform.position.z - obstacle.transform.localScale.z / 2)
- endHeight = int(obstacle.transform.position.z + obstacle.transform.localScale.z / 2)
- # plt.gca().add_patch(
- plt.add_patch(
- patches.Rectangle(
- (startWidth, startHeight),
- endWidth - startWidth,
- endHeight - startHeight,
- fill=False,
- color='black'
- )
- )
- def triangulation_for_triheatmap(M, N):
- """
- triangulation_for_triheatmap divides each pixel of the heatmap into 4 triangles of equal size.
- For this it first determines all x and y coordinates of the vertices of all triangles.
- Then it creates the triangles based on the x and y coordinates.
-
- :param M: The width of the Heatmap
- :param N: The height of the Heatmap
- :return: The created triangles
- """
- xv, yv = np.meshgrid(np.arange(0, M+1), np.arange(0, N+1)) # vertices of the little squares
- xc, yc = np.meshgrid(np.arange(0.5, M), np.arange(0.5, N)) # centers of the little squares
- x = np.concatenate([xv.ravel(), xc.ravel()])
- y = np.concatenate([yv.ravel(), yc.ravel()])
- cstart = (M + 1) * (N + 1) # indices of the centers
- trianglesN = [(i + 1 + (j + 1) * (M + 1), i + (j + 1) * (M + 1), cstart + i + j * M)
- for j in range(N) for i in range(M)]
- trianglesE = [(i + 1 + j * (M + 1), i + 1 + (j + 1) * (M + 1), cstart + i + j * M)
- for j in range(N) for i in range(M)]
- trianglesS = [(i + j * (M + 1), i + 1 + j * (M + 1), cstart + i + j * M)
- for j in range(N) for i in range(M)]
- trianglesW = [(i + (j + 1) * (M + 1), i + j * (M + 1), cstart + i + j * M)
- for j in range(N) for i in range(M)]
-
- return [Triangulation(x, y, triangles) for triangles in [trianglesN, trianglesE, trianglesS, trianglesW]]
- # 1. Get position data from csv file
- data1 = pd.read_csv(POSITION_PATH1, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
- data1 = data1.round(0)
- data2 = pd.read_csv(POSITION_PATH2, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
- data2 = data2.round(0)
- data3 = pd.read_csv(POSITION_PATH3, sep=';', usecols=["Position x", "Position z"], decimal=',', dtype={'Position x': float, 'Position z': float})
- data3 = data3.round(0)
- # 2. Group by positions and count appearance
- data_count1 = data1.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
- data_count2 = data2.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
- data_count3 = data3.groupby(['Position x', 'Position z']).size().reset_index(name='counts')
- # 3. Create wide-form DataFrame for generating heatmap
- positions1 = data_count1.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
- positions2 = data_count2.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
- positions3 = data_count3.loc[:,:].reset_index().pivot(index='Position z', columns='Position x', values='counts')
- # 4. Fill missing values
- positions1.fillna(0, inplace=True)
- positions2.fillna(0, inplace=True)
- positions3.fillna(0, inplace=True)
- # 5. Reindex DataFrame (70,35) size of Surface; (70, 35) first x width then z height
- positions1 = positions1.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
- positions1 = positions1.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
- positions2 = positions2.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
- positions2 = positions2.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
- positions3 = positions3.reindex_axis(range(0, HEIGHT), axis=0, fill_value=0)
- positions3 = positions3.reindex_axis(range(0, WIDTH), axis=1, fill_value=0)
- # 6.1 Get maximum value of positions data
- max1 = pd.DataFrame(positions1).max().max() # Gets the maximum of each column and than the maximum of the maximums
- max2 = pd.DataFrame(positions2).max().max() # Gets the maximum of each column and than the maximum of the maximums
- max3 = pd.DataFrame(positions3).max().max() # Gets the maximum of each column and than the maximum of the maximums
- # 6.2 Get obstacles (obstacles, market stalls) and paste specific value in positions
- # Therefore save pkl file in folder and read from it afterwards
- set_obstacles(max1, OBSTACLE_PATH1)
- set_obstacles(max2, OBSTACLE_PATH2)
- set_obstacles(max3, OBSTACLE_PATH3)
- positions_obstacles = pd.read_pickle(OBSTACLE_PATH1)
- positions_heatmap1 = pd.read_pickle(OBSTACLE_PATH1)
- positions_heatmap2 = pd.read_pickle(OBSTACLE_PATH2)
- positions_heatmap3 = pd.read_pickle(OBSTACLE_PATH3)
- # 7 Merge positions data with obstacles data
- positions_heatmap1.where(positions_heatmap1 != 0, positions1, inplace=True)
- positions_heatmap2.where(positions_heatmap2 != 0, positions2, inplace=True)
- positions_heatmap3.where(positions_heatmap3 != 0, positions3, inplace=True)
- # 7.1 Debug Output
- # positions_heatmap1 = pd.DataFrame(positions_heatmap1)
- # positions_heatmap1.to_html('Assets/Data_image/positions_heatmap1.html')
- # positions_heatmap2 = pd.DataFrame(positions_heatmap2)
- # positions_heatmap2.to_html('Assets/Data_image/positions_heatmap2.html')
- # positions_heatmap3 = pd.DataFrame(positions_heatmap3)
- # positions_heatmap3.to_html('Assets/Data_image/positions_heatmap3.html')
- # 8.1 Divide the pixel in four equal triangles and plot heatmap
- values = [positions_heatmap1, positions_heatmap2, positions_obstacles, positions_heatmap3]
- triangul = triangulation_for_triheatmap(WIDTH, HEIGHT)
- cmaps = [LinearSegmentedColormap.from_list(name='2019', colors=['grey', (0.40,0.76,0.65), (0.11,0.62,0.47)]),
- LinearSegmentedColormap.from_list(name='2020', colors=['grey', (0.99,0.55,0.38), (0.85,0.37,0.01)]),
- LinearSegmentedColormap.from_list(name='2021', colors=['grey', (1, 1, 0.975)]),
- LinearSegmentedColormap.from_list(name='2021', colors=['grey', (0.55,0.63,0.80), (0.46,0.44,0.70)])]
- fig, ax = plt.subplots()
- imgs = [ax.tripcolor(t, np.ravel(val), cmap=cmap)
- for t, val, cmap in zip(triangul, values, cmaps)]
- # 8.2 Mark the Market stalls
- set_patches(ax)
- # 9.1 Set visual settings
- ax.tick_params(axis='both', which='both', bottom='off', labelbottom='off', left='off', labelleft='off')
- ax.margins(x=0, y=0)
- ax.set_aspect('equal', 'box') # square cells
- plt.tight_layout()
- # 9.2 Show Heatmap
- plt.show()
- # 10. Save Heatmap
- fig.savefig(HEATMAP_PATH, transparent=True)
|