This commit is contained in:
Alexandre Foucher 2024-10-03 15:47:52 +02:00
parent bc69ad483b
commit f85f23776a
7 changed files with 208 additions and 108 deletions

74
evaluate/eval.py Normal file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
import math
import cv2 as cv
import pyskyline
import numpy as np
import argparse
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
def show_result(terrain, skyline, coord, scoremap):
fig = plt.figure()
gs = fig.add_gridspec(2,3)
fig.suptitle("Mosse correlation", fontsize=16)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[0, 2])
ax4 = fig.add_subplot(gs[1, :])
im1 = ax1.imshow(scoremap, cmap='hot', interpolation='nearest')
ax1.set_title('Correlation map')
cbar1 = ax1.figure.colorbar(im1)
cbar1.ax.set_ylabel("Correlation score", rotation=-90, va="bottom")
rgb = cv.cvtColor(terrain.rgb(), cv.COLOR_BGR2RGB)
rgb = cv.circle(rgb, coord, 2, (255,0,0), -1)
ax2.imshow(rgb)
ax2.set_title('Colored terrain')
im2 = ax3.imshow((terrain.grayscale().astype(float) - 135) * (40.0/120.0))
ax3.set_title('DEM')
cbar = ax1.figure.colorbar(im2)
cbar.ax.set_ylabel("Altitude", rotation=-90, va="bottom")
x = np.linspace(-180,180,skyline.shape[0])
ax4.plot(x,skyline,label="Agent vision")
ax4.plot(x,terrain.get_skyline(coord[0], coord[1]), label="DEM data")
ax4.legend()
plt.show()
def eval_score(correlation_map, coord, scale, alpha=10):
alpha = alpha / scale
score = 0
h,w = correlation_map.shape
x,y = coord
for j in range(h):
for i in range(w):
dist = math.sqrt((x-i)**2+(y-j)**2)
score += correlation_map[j,i] * 1 #(math.pow(0.5,math.pow(dist/alpha,2)-1)-1)
return score
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--size", help="Terrain size (default=128)", type=int, default=128)
parser.add_argument("--seed", help="Terrain seed (If not set seed is random)", type=int)
args = parser.parse_args()
seed = np.random.randint(0,1024) if args.seed is None else args.seed
terrain = pyskyline.Terrain.generate(size=args.size,seed=seed,scale=8.0,max_altitude=40.0)
terrain.precompute_all_skylines()
x, y = terrain.get_random_water_coordinate()
skyline = terrain.get_skyline(x, y)
angle = np.random.randint(1,40)
skyline = np.roll(skyline, angle)
correlation_map = pyskyline.MosseCorrelation.process(terrain, skyline)
score = eval_score(correlation_map,(x,y), terrain._scale,100)
score = score / (2 * terrain.water_tile_count()) + 0.5
print(f"{score:0.2f} with angle of {angle}")
show_result(terrain, skyline, (x,y), correlation_map)

View file

@ -1,11 +1,9 @@
import numpy as np
import scipy.signal as signal
from scipy.fftpack import fft, fftshift, ifft
from scipy.fftpack import fft, ifft
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
N = 256
def gaussian_filter1d(size,sigma):
filter_range = np.linspace(-int(size/2),int(size/2),size)
gaussian_filter = [1 / (sigma * np.sqrt(2*np.pi)) * np.exp(-x**2/(2*sigma**2)) for x in filter_range]
@ -18,117 +16,115 @@ def generate_signal(N:int) -> np.ndarray:
y[i] = np.random.normal(scale=1) + (y[i-1] if i > 1 else 0)
return np.convolve(y,gaussian_filter1d(N,1),'same')
if __name__ == '__main__':
class UI:
def __init__(self) -> None:
self._size_F = 256
self._size_H = 256
self._sigma = 1.1
self._seed = 0
self._shift = 0
self._fig, self._axs = self.init_ui()
np.random.seed(42)
f_full = generate_signal(2048)
x = np.arange(-180,180,360/N)
f = f_full[1024:1024+N]
h = f_full[1024:1024+N]
g = signal.gaussian(N, std=10,sym=True)
self.update()
F = fft(f)
F_ = np.conjugate(F)
G = fft(g)
def init_ui(self) -> None:
fig, (ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8) = plt.subplots(8, 1, gridspec_kw={'height_ratios':[4,4,4,1,1,1,1,1]})
K_ = (G*F_)/(F*F_)
ax1.set_title('Terrain')
self.plt_f, = ax1.plot([])
self.plt_h, = ax1.plot([])
H = fft(h)
r = ifft(H*K_)
ax2.set_title('MOSSE response signal')
ax2.set_ylim([0, 1.2])
self.line_r = ax2.axvline()
self.plt_r, = ax2.plot([])
self.plt_r2, = ax2.plot([])
# ==========================================
ax3.set_title('Gaussian')
self.plt_g, = ax3.plot([])
fig, (ax1, ax2, ax3, ax4, ax5, ax6, ax7) = plt.subplots(7, 1, gridspec_kw={'height_ratios':[4,4,4,1,1,1,1]})
ax1.set_xlim([-180,180])
ax2.set_xlim([-180,180])
ax3.set_xlim([-180,180])
ax1.set_title('Terrain')
plt_f, = ax1.plot(x,f)
plt_h, = ax1.plot(x,h)
self._slider1 = Slider(ax4, 'sigma', 0.1, 5, valinit=self._sigma)
self._slider2 = Slider(ax5, 'shift', -180, 180, valinit=self._shift, valstep=1)
self._slider3 = Slider(ax6, 'seed', 0, 50, valinit=self._seed, valstep=1)
self._slider4 = Slider(ax7, 'Size f', 64, 1024, valinit=self._size_F, valstep=8)
self._slider5 = Slider(ax8, 'Size h', 64, 1024, valinit=self._size_H, valstep=8)
ax2.set_title('MOSSE response signal')
ax2.set_ylim([0, 1.2])
line_r = ax2.axvline(x=-N//2+np.argmax(abs(r)), color='r')
plt_r, = ax2.plot(x,abs(r))
plt_r2, = ax2.plot(x,abs(r))
self._slider1.on_changed(self.update_sigma)
self._slider2.on_changed(self.update_shift)
self._slider3.on_changed(self.update_seed)
self._slider4.on_changed(self.update_size_f)
self._slider5.on_changed(self.update_size_h)
ax3.set_title('Gaussian')
plt_g, = ax3.plot(x,g)
fig.subplots_adjust(hspace=0.75)
ax1.set_xlim([-180,180])
ax2.set_xlim([-180,180])
ax3.set_xlim([-180,180])
return fig, [ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8]
slider1 = Slider(ax4, 'sigma', 0.3, 10, valinit=0.1)
slider2 = Slider(ax5, 'shift', -N//2 , N//2, valinit=0, valstep=1)
slider3 = Slider(ax6, 'seed', 0 , 50, valinit=0, valstep=1)
slider4 = Slider(ax7, 'N', 128 , 1024, valinit=256, valstep=8)
sigma = 0.3
shift = 0
def update():
# K_ = (G*F_)/(F*F_)
window = np.ones((N)) #signal.windows.hamming(N)
H = fft(h*window)
F = fft(f*window)
def update(self):
np.random.seed(self._seed)
g = signal.windows.gaussian(self._size_F, std=self._sigma,sym=True)
f = generate_signal(self._size_F) * signal.windows.hamming(self._size_F)
h = np.zeros((self._size_F))
s = self._size_F//2-self._size_H//2
h[s:s+self._size_H] = (f[s:s+self._size_H] + np.random.normal(0,0.5, self._size_H)) * signal.windows.hamming(self._size_H)
h = np.roll(h,int(self._shift/180*self._size_F//2))
F = fft(f)
G = fft(g)
H = fft(h)
R = H*G/F
r = ifft(R)
s = np.argmax(abs(r))
r2 = np.copy(r)
r2[s-5:s+5] = 0
plt_g.set_data(x,g)
plt_r.set_data(x,abs(r))
plt_r2.set_data(x,abs(r2))
plt_h.set_data(x,h)
plt_f.set_data(x,f)
ax1.set_ylim([min(np.min(h),np.min(f))-1, max(np.max(h),np.max(f))+1])
ax2.set_ylim([0, np.max(r)+0.2])
line_r.set_xdata(round((np.argmax(abs(r))/N-0.5)*360))
fig.canvas.draw_idle()
x = np.linspace(-180,180,self._size_F)
self.plt_g.set_data(x, g)
self.plt_r.set_data(x,abs(r))
self.plt_r2.set_data(x,abs(r2))
self.plt_f.set_data(x,f)
self.plt_h.set_data(x,h)
self._axs[0].set_ylim([min(np.min(h),np.min(f))-1, max(np.max(h),np.max(f))+1])
self._axs[1].set_ylim([0, np.max(r)+0.2])
self._axs[2].set_ylim([0, np.max(g)*1.1])
self.line_r.set_xdata([round((np.argmax(abs(r))/self._size_F-0.5)*360)])
self._fig.canvas.draw_idle()
def update_sigma(val):
global g, G, sigma, N
sigma = val
g = signal.gaussian(N, std=sigma,sym=True)
G = fft(g)
update()
def update_sigma(self, val):
self._sigma = val
self.update()
def update_size_f(self, val):
if val < self._size_H:
self._slider4.eventson = False
self._slider4.set_val(max(val, self._size_H))
self._slider4.eventson = True
self._size_F = max(val, self._size_H)
self.update()
def update_shift(val):
global shift, H, h
shift = -val
h = f_full[1024+round(shift*(N/360)):1024+N+round(shift*(N/360))]
noise = np.random.normal(0,0.5, N)
h = h+noise
update()
def update_size_h(self, val):
if val > self._size_F:
self._slider5.eventson = False
self._slider5.set_val(min(val, self._size_F))
self._slider5.eventson = True
self._size_H = min(val, self._size_F)
self.update()
def update_seed(val):
global f_full, f, h, F, H, F_, shift
np.random.seed(val)
f_full = generate_signal(2048)
f = f_full[1024:1024+N]
h = f_full[1024+round(shift*(N/360)):1024+N+round(shift*(N/360))]
noise = np.random.normal(0,0.5, N)
h = h+noise
update()
def update_seed(self, val):
self._seed = val
self.update()
def update_n(val):
global g, G, N, f_full, f, h, F, H, F_, shift, x, sigma, slider2
N = val
x = np.arange(-180,180,360/N)
g = signal.gaussian(N, std=sigma,sym=True)
G = fft(g)
f = f_full[1024:1024+N]
h = f_full[1024+shift:1024+N+shift]
noise = np.random.normal(0,0.5, N)
h = h+noise
def update_shift(self, val):
self._shift = val
self.update()
update()
def show(self) -> None:
plt.show()
slider1.on_changed(update_sigma)
slider2.on_changed(update_shift)
slider3.on_changed(update_seed)
slider4.on_changed(update_n)
plt.subplots_adjust(hspace=0.5)
plt.show()
if __name__ == '__main__':
ui = UI()
ui.show()

View file

@ -1,2 +1,4 @@
noise==1.2.2
scipy==1.14.1
scipy==1.14.1
matplotlib
pyqt5

View file

@ -17,7 +17,7 @@ namespace skyline
std::uint16_t m_raycount;
float m_raydist;
float m_raystep;
std::map<std::uint64_t, float*> m_skylines;
std::map<std::uint32_t, float*> m_skylines;
float *compute_skyline(std::int32_t x, std::int32_t y);
@ -25,7 +25,7 @@ namespace skyline
Terrain(std::size_t size, std::uint8_t *dem, std::uint8_t ocena_height, float max_altitude, float scale, std::uint16_t raycount, float raydist);
~Terrain();
std::size_t get_size();
float *get_skyline(std::int32_t x, std::int32_t y);
float *get_skyline(std::uint32_t x, std::uint32_t y);
bool is_water(std::int32_t x, std::int32_t y);
void precompute_all_skylines();
};

View file

@ -1,6 +1,7 @@
# include "Terrain.hpp"
# include <math.h>
# include <iostream>
# include <vector>
// ============================================================================
// P R I V A T E
@ -19,7 +20,6 @@ float *skyline::Terrain::compute_skyline(std::int32_t x, std::int32_t y)
int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2;
std::uint8_t height = 0;
float max_angle = 0;
float dist, angle;
@ -78,9 +78,9 @@ bool skyline::Terrain::is_water(int32_t x, int32_t y)
return this->m_dem[y * this->m_size + x] <= this->m_ocean_height;
}
float *skyline::Terrain::get_skyline(std::int32_t x, std::int32_t y)
float *skyline::Terrain::get_skyline(std::uint32_t x, std::uint32_t y)
{
std::uint64_t id = y * this->m_size + x;
std::uint64_t id = (y << 16) | x;
// Check if skyline is already computed
if (this->is_water(x,y))
@ -99,20 +99,33 @@ float *skyline::Terrain::get_skyline(std::int32_t x, std::int32_t y)
void skyline::Terrain::precompute_all_skylines()
{
std::vector<std::uint32_t> sharedList;
#pragma omp parallel for collapse(2)
for( std::uint32_t x=0; x<this->m_size; x++)
for( int x=0; x<this->m_size; x++)
{
for( std::uint32_t y=0; y<this->m_size; y++)
for( int y=0; y<this->m_size; y++)
{
if (this->is_water(x,y))
{
std::uint64_t id = y * this->m_size + x;
if (this->m_skylines.find(id) == this->m_skylines.end()) {
this->m_skylines.insert({id, this->compute_skyline(x, y)});
}
#pragma omp critical
sharedList.push_back(y << 16 | x);
}
}
}
#pragma omp parallel for
for( int i=0; i<sharedList.size(); i++)
{
std::uint32_t id = sharedList.at(i);
std::uint16_t y = (0xFFFF0000 & id) >> 16;
std::uint16_t x = (0x0000FFFF & id);
float *skyline = this->compute_skyline(x, y);
#pragma omp critical
this->m_skylines.insert({id, skyline});
}
}
extern "C" {

View file

@ -54,4 +54,4 @@ class MosseCorrelation:
if print_progress:
progress_bar.stop()
return score_map
return score_map/np.max(score_map)

View file

@ -1,6 +1,8 @@
import numpy as np
import noise
import ctypes
import random
from scipy import ndimage
from . import DEFAULT_RAYCOUNT, DEFAULT_RAYDIST
@ -73,12 +75,25 @@ class Terrain():
_LIB.Terrain_precompute_all_skylines(self.obj)
print(f"{(time.time()-a):.1f}s")
def get_random_water_coordinate(self, coastal_range=100.0) -> tuple:
r = int(round(coastal_range/self._scale))
valid_location = self._dem <= GROUND_LAYER['ocean']
valid_location = ndimage.binary_erosion(valid_location, structure=np.ones((r,r)))
while True:
x = random.randint(0, self._size)
y = random.randint(0, self._size)
if valid_location[y,x] == 1:
return (x,y)
def rgb(self) -> np.ndarray:
rgb = np.full((self._size, self._size, 3), COLOR_WATER, dtype=np.uint8)
rgb[self._dem > GROUND_LAYER['ocean']] = COLOR_SAND
rgb[self._dem > GROUND_LAYER['sand']] = COLOR_GRASS
rgb[self._dem > GROUND_LAYER['grass']] = COLOR_TREE
return rgb
return np.asarray(rgb)
def grayscale(self) -> np.ndarray:
return self._dem
def water_tile_count(self) -> int:
return (self._dem <= GROUND_LAYER['ocean']).sum()