Files
GDMC-2024/world_maker/data_analysis.py

468 lines
17 KiB
Python

from world_maker.World import World
from PIL import Image, ImageFilter
import numpy as np
from scipy import ndimage
from world_maker.Skeleton import Skeleton
from world_maker.Position import Position
from random import randint, choice
import cv2
def get_data(world: World):
print("[Data Analysis] Generating data...")
heightmap, watermap, treemap = world.getData()
heightmap.save('./world_maker/data/heightmap.png')
watermap.save('./world_maker/data/watermap.png')
treemap.save('./world_maker/data/treemap.png')
print("[Data Analysis] Data generated.")
return heightmap, watermap, treemap
def get_data_no_update():
print("[Data Analysis] Generating data...")
# heightmap, watermap, treemap = world.getData()
heightmap, watermap, treemap = handle_import_image(
'./world_maker/data/heightmap.png'), handle_import_image(
'./world_maker/data/watermap.png'), handle_import_image(
'./world_maker/data/treemap.png')
print("[Data Analysis] Data generated.")
return heightmap, watermap, treemap
def handle_import_image(image: str | Image.Image) -> Image.Image:
if isinstance(image, str):
return Image.open(image)
return image
def filter_negative(image: str | Image.Image) -> Image.Image:
"""
Invert the colors of an image.
Args:
image (image): image to filter
"""
image = handle_import_image(image)
return Image.fromarray(np.invert(np.array(image)))
def filter_sobel(image: str | Image.Image) -> Image.Image:
"""
Edge detection algorithms from an image.
Args:
image (image): image to filter
"""
# Open the image
image = handle_import_image(image).convert('RGB')
img = np.array(image).astype(np.uint8)
# Apply gray scale
gray_img = np.round(
0.299 * img[:, :, 0] + 0.587 * img[:, :, 1] + 0.114 * img[:, :, 2]
).astype(np.uint8)
# Sobel Operator
h, w = gray_img.shape
# define filters
horizontal = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # s2
vertical = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # s1
# define images with 0s
newhorizontalImage = np.zeros((h, w))
newverticalImage = np.zeros((h, w))
newgradientImage = np.zeros((h, w))
# offset by 1
for i in range(1, h - 1):
for j in range(1, w - 1):
horizontalGrad = (
(horizontal[0, 0] * gray_img[i - 1, j - 1])
+ (horizontal[0, 1] * gray_img[i - 1, j])
+ (horizontal[0, 2] * gray_img[i - 1, j + 1])
+ (horizontal[1, 0] * gray_img[i, j - 1])
+ (horizontal[1, 1] * gray_img[i, j])
+ (horizontal[1, 2] * gray_img[i, j + 1])
+ (horizontal[2, 0] * gray_img[i + 1, j - 1])
+ (horizontal[2, 1] * gray_img[i + 1, j])
+ (horizontal[2, 2] * gray_img[i + 1, j + 1])
)
newhorizontalImage[i - 1, j - 1] = abs(horizontalGrad)
verticalGrad = (
(vertical[0, 0] * gray_img[i - 1, j - 1])
+ (vertical[0, 1] * gray_img[i - 1, j])
+ (vertical[0, 2] * gray_img[i - 1, j + 1])
+ (vertical[1, 0] * gray_img[i, j - 1])
+ (vertical[1, 1] * gray_img[i, j])
+ (vertical[1, 2] * gray_img[i, j + 1])
+ (vertical[2, 0] * gray_img[i + 1, j - 1])
+ (vertical[2, 1] * gray_img[i + 1, j])
+ (vertical[2, 2] * gray_img[i + 1, j + 1])
)
newverticalImage[i - 1, j - 1] = abs(verticalGrad)
# Edge Magnitude
mag = np.sqrt(pow(horizontalGrad, 2.0) + pow(verticalGrad, 2.0))
newgradientImage[i - 1, j - 1] = mag
image = Image.fromarray(newgradientImage)
image = image.convert("L")
return image
def filter_smooth_theshold(image: str | Image.Image, radius: int = 3):
"""
:param image: white and black image representing the derivative of the terrain (sobel), where black is flat and white is very steep.
:param radius: Radius of the Gaussian blur.
Returns:
image: black or white image, with black as flat areas to be skeletonized
"""
image = handle_import_image(image)
# image = image.filter(ImageFilter.SMOOTH_MORE)
# image = image.filter(ImageFilter.SMOOTH_MORE)
# image = image.filter(ImageFilter.SMOOTH_MORE)
image = image.convert('L')
image = image.filter(ImageFilter.GaussianBlur(radius))
array = np.array(image)
bool_array = array > 7
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((3,3)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((3,3)), iterations=1)
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((5,5)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((5,5)), iterations=1)
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((7,7)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((7,7)), iterations=1)
return Image.fromarray(bool_array)
def filter_smooth(image: str | Image.Image, radius: int = 3):
image = handle_import_image(image)
image = image.convert('L')
image = image.filter(ImageFilter.GaussianBlur(radius))
return image
def subtract_map(image: str | Image.Image, substractImage: str | Image.Image) -> Image.Image:
image = handle_import_image(image)
substractImage = handle_import_image(substractImage).convert('L')
array_heightmap = np.array(image)
array_substractImage = np.array(substractImage)
mask = array_substractImage == 255
array_heightmap[mask] = 0
return Image.fromarray(array_heightmap)
def overide_map(base: Image, top: Image) -> Image.Image:
base = handle_import_image(base).convert('L')
top = handle_import_image(top).convert('L')
width, height = base.size
if top.size != (width, height):
raise ValueError("Mismatching images sizes")
result_image = Image.new('L', (width, height))
for x in range(width):
for y in range(height):
base_pixel = base.getpixel((x, y))
top_pixel = top.getpixel((x, y))
if top_pixel != 0:
result_image.putpixel((x, y), top_pixel)
else:
result_image.putpixel((x, y), base_pixel)
return result_image
def group_map(image1: str | Image.Image, image2: str | Image.Image) -> Image.Image:
image1 = handle_import_image(image1).convert('L')
image2 = handle_import_image(image2).convert('L')
array1 = np.array(image1)
array2 = np.array(image2)
mask = array1 == 255
array2[mask] = 255
return Image.fromarray(array2)
def filter_smooth_array(array: np.ndarray, radius: int = 3) -> np.ndarray:
image = Image.fromarray(array)
smooth_image = filter_smooth_theshold(image, radius)
array = np.array(smooth_image)
return array
def filter_remove_details(image: str | Image.Image, n: int = 20) -> Image.Image:
image = handle_import_image(image)
array = np.array(image)
for _ in range(n):
array = ndimage.binary_dilation(array, iterations=4)
array = ndimage.binary_erosion(array, iterations=5)
array = filter_smooth_array(array, 2)
array = ndimage.binary_erosion(array, iterations=3)
image = Image.fromarray(array)
return image
def highway_map() -> Image.Image:
print("[Data Analysis] Generating highway map...")
smooth_sobel = filter_smooth_theshold("./world_maker/data/sobelmap.png", 1)
negative_smooth_sobel = filter_negative(smooth_sobel)
negative_smooth_sobel_water = subtract_map(
negative_smooth_sobel, './world_maker/data/watermap.png')
array_sobel_water = np.array(negative_smooth_sobel_water)
# Remove details
array_sobel_water = ndimage.binary_erosion(
array_sobel_water, iterations=5)
# Smooth non buildable area
array_sobel_water = filter_negative(
filter_smooth_array(np.array(filter_negative(array_sobel_water)), 2))
array_sobel_water = np.array(array_sobel_water)
# Propagate buildable area
array_sobel_water = ndimage.binary_dilation(
array_sobel_water, iterations=5)
# Smooth buildable area
array_sobel_water = filter_smooth_array(array_sobel_water, 2)
array_sobel_water = np.array(array_sobel_water)
# Smooth non buildable area
array_sobel_water = filter_negative(
filter_smooth_array(np.array(filter_negative(array_sobel_water)), 1))
array_sobel_water = np.array(array_sobel_water)
# Erode buildable area
array_sobel_water = ndimage.binary_erosion(
array_sobel_water, iterations=12)
# Dilate buildable area
array_sobel_water = ndimage.binary_dilation(
array_sobel_water, iterations=7)
# Smooth buildable area
array_sobel_water = filter_smooth_array(array_sobel_water, 20)
array_sobel_water = np.array(array_sobel_water)
image = Image.fromarray(array_sobel_water)
image.save('./world_maker/data/smooth_for_highway.png')
# Remove details
# image_no_details = filter_remove_details(image, 10)
# image_no_details.save('./world_maker/data/highwaymap.png')
print("[Data Analysis] Highway map generated.")
return image
def create_volume(surface: np.ndarray, heightmap: np.ndarray, make_it_flat: bool = False) -> np.ndarray:
volume = np.full((len(surface[0]), 255, len(surface)), False)
for z in range(len(surface)):
for x in range(len(surface[0])):
if not make_it_flat:
volume[x][heightmap[z][x]][z] = surface[z][x]
else:
volume[x][0][z] = surface[z][x]
return volume
def convert_2D_to_3D(image: str | Image.Image, make_it_flat: bool = False) -> np.ndarray:
image = handle_import_image(image)
heightmap = Image.open(
'./world_maker/data/heightmap_smooth.png').convert('L')
heightmap = np.array(heightmap)
surface = np.array(image)
volume = create_volume(surface, heightmap, make_it_flat)
return volume
def skeleton_highway_map(image: str | Image.Image = './world_maker/data/highwaymap.png') -> Skeleton:
image_array = convert_2D_to_3D(image, True)
skeleton = Skeleton(image_array)
skeleton.parse_graph(True)
heightmap_skeleton = skeleton.map()
heightmap_skeleton.save('./world_maker/data/skeleton_highway.png')
skeleton.road_area('skeleton_highway_area.png', 10)
return skeleton
def skeleton_mountain_map(image: str | Image.Image = './world_maker/data/mountain_map.png') -> Skeleton:
image_array = convert_2D_to_3D(image, True)
skeleton = Skeleton(image_array)
skeleton.parse_graph()
heightmap_skeleton = skeleton.map()
heightmap_skeleton.save('./world_maker/data/skeleton_mountain.png')
skeleton.road_area('skeleton_mountain_area.png', 3)
return skeleton
def smooth_sobel_water(image: str | Image.Image = "./world_maker/data/sobelmap.png") -> Image.Image:
watermap = handle_import_image("./world_maker/data/watermap.png")
watermap = filter_negative(
filter_remove_details(filter_negative(watermap), 5))
sobel = handle_import_image(image)
sobel = filter_remove_details(filter_smooth_theshold(sobel, 1), 5)
sobel_array = np.array(filter_negative(sobel))
sobel_array = ndimage.binary_dilation(sobel_array, iterations=3)
sobel = filter_negative(Image.fromarray(sobel_array))
group = group_map(watermap, sobel)
group = filter_negative(group)
group.save('./world_maker/data/smooth_sobel_watermap.png')
return group
def mountain_map_expend(mountain_map: list[list[int]], starting_point: tuple[int, int], value: int):
explore_points = [starting_point]
while len(explore_points) > 0:
x, y = explore_points.pop(0)
mountain_map[y][x] = value
for i in range(-1, 2):
for j in range(-1, 2):
if (0 <= x + i < len(mountain_map[0]) and 0 <= y + j < len(mountain_map) and
mountain_map[y + j][x + i] == 0):
if (x + i, y + j) not in explore_points:
explore_points.append((x + i, y + j))
def set_values_of_building_mountain(mountain_map: list[list[int]], area_mountain: list[int],
building_map: str | Image.Image = "./world_maker/data/smooth_sobel_watermap.png"):
building_map = handle_import_image(building_map).convert('L')
for y in range(building_map.size[1]):
for x in range(building_map.size[0]):
if building_map.getpixel((x, y)) > 144:
if mountain_map[y][x] != -1:
area_mountain[mountain_map[y][x] - 1] += 1
def get_index_of_biggest_area_mountain(area_mountain: list[int], exception: list[int]) -> int:
max_value = -1
index = -1
for i in range(len(area_mountain)):
if i not in exception and area_mountain[i] > max_value:
max_value = area_mountain[i]
index = i
return index
def get_random_point_in_area_mountain(mountain_map: list[list[int]], index: int) -> Position | None:
points = []
for y in range(len(mountain_map)):
for x in range(len(mountain_map[0])):
if mountain_map[y][x] == index + 1:
points.append(Position(x, y))
if not points:
return None
return choice(points)
def get_center_of_area_mountain(mountain_map: list[list[int]], index: int) -> Position:
sum_x = 0
sum_y = 0
count = 0
for y in range(len(mountain_map)):
for x in range(len(mountain_map[0])):
if mountain_map[y][x] == index + 1:
sum_x += x
sum_y += y
count += 1
center = Position(sum_x // count, sum_y // count)
if mountain_map[center.y][center.x] != index + 1:
return get_random_point_in_area_mountain(mountain_map, index)
return center
def detect_mountain(number_of_mountain: int = 2, height_threshold: int = 10,
image_heightmap: str | Image.Image = './world_maker/data/heightmap.png') -> list[Position]:
print("[Data Analysis] Detecting mountains...")
image_heightmap = handle_import_image(image_heightmap).convert('L')
avg_height = 0
for y in range(image_heightmap.size[1]):
for x in range(image_heightmap.size[0]):
avg_height += image_heightmap.getpixel((x, y))
avg_height = int(
avg_height / (image_heightmap.size[0] * image_heightmap.size[1]))
print("[Data Analysis] Average height:", avg_height)
mountain_map = [[-1 if image_heightmap.getpixel((x, y)) < (avg_height + height_threshold) else 0 for x in
range(image_heightmap.size[0])] for y in
range(image_heightmap.size[1])]
area_mountain = []
for y in range(image_heightmap.size[1]):
for x in range(image_heightmap.size[0]):
if mountain_map[y][x] == 0:
area_mountain.append(0)
mountain_map_expend(mountain_map, (x, y), len(area_mountain))
if not area_mountain:
print("[Data Analysis] No mountain detected.")
return []
set_values_of_building_mountain(mountain_map, area_mountain)
if number_of_mountain < len(area_mountain):
index_mountain = []
for n in range(number_of_mountain):
index_mountain.append(get_index_of_biggest_area_mountain(
area_mountain, index_mountain))
else:
index_mountain = [i for i in range(len(area_mountain))]
position_mountain = []
for i in range(len(index_mountain)):
position_mountain.append(get_center_of_area_mountain(
mountain_map, index_mountain[i]))
return position_mountain
def rectangle_2D_to_3D(rectangle: list[tuple[tuple[int, int], tuple[int, int]]],
height_min: int = 6, height_max: int = 10) \
-> list[tuple[tuple[int, int, int], tuple[int, int, int]]]:
image = handle_import_image(
'./world_maker/data/heightmap.png').convert('L')
new_rectangle = []
for rect in rectangle:
start, end = rect
height = {}
for x in range(start[0], end[0]):
for y in range(start[1], end[1]):
if image.getpixel((x, y)) not in height:
height[image.getpixel((x, y))] = 1
else:
height[image.getpixel((x, y))] += 1
max_height = max(height, key=height.get)
new_rectangle.append(
((start[0], max_height, start[1]), (end[0], max_height + randint(height_min, height_max), end[1])))
return new_rectangle
def transpose_form_heightmap(heightmap: str | Image.Image, coordinates, origin: tuple[int, int]) -> tuple[
int, int, int]:
heightmap = handle_import_image(heightmap).convert('L')
xMin, zMin = origin
return (coordinates[0] + xMin, heightmap.getpixel(
(coordinates[0], coordinates[-1])), coordinates[-1] + zMin)