PabloVD commited on
Commit
bf74b80
·
1 Parent(s): 545f1ba

Include app and source code

Browse files
Files changed (6) hide show
  1. README.md +25 -0
  2. app.py +76 -0
  3. requirements.txt +3 -0
  4. source/fields.py +168 -0
  5. source/maps.py +91 -0
  6. source/visualization_tools.py +68 -0
README.md CHANGED
@@ -11,4 +11,29 @@ license: mit
11
  short_description: Generate procedural geographic maps from random fields
12
  ---
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
11
  short_description: Generate procedural geographic maps from random fields
12
  ---
13
 
14
+ # MapGenerator
15
+
16
+ Generate procedural geographic maps from random fields.
17
+
18
+ ## Map generation
19
+
20
+ The process of map generation is as follows:
21
+
22
+ 1. Generate a random field, choosing from the list of available random fields
23
+ 2. Normalize the field between 0 and 1
24
+ 3. Smooth the field with a gaussian filter
25
+ 4. Retain only the mainland above a certain threshold
26
+
27
+ ## Random fields
28
+
29
+ The available random fields are:
30
+
31
+ - `gauss`: Random gaussian field, with a given power spectrum, computed using the package [powerbox](https://powerbox.readthedocs.io/en/latest/index.html)
32
+ - `perlin`: Perlin noise, computed using the package [noise](https://pypi.org/project/noise/)
33
+ - `warped_perlin`: Perlin noise with domain warping, computed using the package [noise](https://pypi.org/project/noise/)
34
+ - `cos`: Sinusoidal noise (to be improved)
35
+ - `fbm`: Fractional Brownian Field
36
+
37
+ **See complete source code [here](https://github.com/PabloVD/MapGenerator/tree/master)**
38
+
39
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from source.visualization_tools import single_map
3
+ from PIL import Image
4
+ import io
5
+ import random
6
+
7
+ # Threshold for the sea level
8
+ threshold = 0.6
9
+ # Sigma for the gaussian smoothing
10
+ sigma = 5.
11
+
12
+ def generate_maps(kind_noise,boxsize,index,scale,octaves,persistence,lacunarity,make_island,deterministic):
13
+
14
+ if kind_noise=="gauss":
15
+ params = index
16
+ else:
17
+ params = [scale,octaves,persistence,lacunarity,boxsize]
18
+
19
+ if deterministic:
20
+ seeds = range(3)
21
+ else:
22
+ seeds = random.sample(range(1000),3)
23
+
24
+ images = []
25
+
26
+ for llavor in seeds:
27
+ fig = single_map(kind_noise,boxsize,llavor,params,sigma,threshold,make_island=make_island)
28
+ img_buf = io.BytesIO()
29
+ fig.savefig(img_buf, format='png')
30
+
31
+ img = Image.open(img_buf)
32
+ images.append(img)
33
+
34
+ return images
35
+
36
+ md ="""
37
+ # Map generator
38
+
39
+ Generate procedural geographic maps from random fields.
40
+ """
41
+
42
+
43
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
44
+
45
+ gr.Markdown(md)
46
+
47
+ with gr.Accordion("General settings", open=True):
48
+
49
+ with gr.Row():
50
+ kind_noise = gr.Dropdown(["gauss", "perlin", "warped_perlin"], label="Random field", value="gauss")
51
+ boxsize = gr.Slider(100, 1000, value=500, label="Box size")#, info="Box size"),
52
+ make_island = gr.Checkbox(label="Island", info="Mark to ensure that boundaries are sea")
53
+ deterministic = gr.Checkbox(label="Deterministic", info="Mark to employ the same random seed")
54
+
55
+ with gr.Accordion("Gaussian field settings", open=False):
56
+ index = gr.Slider(-5, -1, value=-3, label="Spectral index")#, info="Spectral index"),
57
+
58
+ with gr.Accordion("Perlin field settings", open=False):
59
+ with gr.Row():
60
+ scale = gr.Slider(100, 1000, value=500, label="Scale")
61
+ octaves = gr.Slider(1, 10, value=6, label="Octaves", step=1)
62
+ persistence = gr.Slider(0, 1, value=0.5, label="Persistence")
63
+ lacunarity = gr.Slider(0.1, 10, value=2, label="Lacunarity")
64
+
65
+
66
+ inputs = [kind_noise,boxsize,index,scale,octaves,persistence,lacunarity,make_island,deterministic]
67
+
68
+ btn = gr.Button("Generate maps", scale=1)
69
+
70
+ gallery = gr.Gallery(label="Generated maps", show_label=False, elem_id="gallery", columns=[3], rows=[1], height="20vw")
71
+
72
+ btn.click(generate_maps, inputs=inputs, outputs=gallery)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ powerbox
2
+ noise
3
+ scipy
source/fields.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #---------------------------
2
+ # Field generator module
3
+ # PabloVD
4
+ # Started: 11/5/20
5
+ #---------------------------
6
+
7
+ """
8
+ Collection of noise fields for generating maps.
9
+ Noises included are:
10
+ "gauss": Random gaussian field, with a given power spectrum, computed using the package powerbox
11
+ "perlin": Perlin noise, computed using the package noise
12
+ "warped_perlin": Perlin noise with domain warping, computed using the package noise
13
+ "cos": Sinusoidal noise (to be improved)
14
+ "fbm": Fractional Brownian Field
15
+ """
16
+
17
+ import numpy as np
18
+ import powerbox as pbox
19
+ import noise
20
+
21
+ # Define power spectrum as a power law with an spectral index indexlaw
22
+ # With lower the spectral indexes (redder noise), small structures are removed
23
+ def powerspec(k,indexlaw=-3.):
24
+ return k**indexlaw
25
+
26
+ # Generate a Gaussian field with a power law power spectrum
27
+ def gaussian_field(boxsize,seed,indexlaw=-3.):
28
+ field = pbox.PowerBox(boxsize, lambda k: powerspec(k,indexlaw), dim=2, boxlength=1.,seed=seed).delta_x()
29
+ return field
30
+
31
+ # Generate a Perlin field
32
+ def perlin_field(boxsizex,seed,scale,octaves,persistence,lacunarity,boxsizey=None):
33
+
34
+ if boxsizey==None: boxsizey=boxsizex
35
+ shape = (boxsizex,boxsizey)
36
+
37
+ field = np.zeros(shape)
38
+ for i in range(shape[0]):
39
+ for j in range(shape[1]):
40
+ field[i,j] = noise.pnoise2(i/scale,
41
+ j/scale,
42
+ octaves=octaves,
43
+ persistence=persistence,
44
+ lacunarity=lacunarity,
45
+ #repeatx=1024,
46
+ #repeaty=1024,
47
+ base=seed)
48
+ return field
49
+
50
+ # 2D cosinus (see Hill noise for something similar but better https://blog.bruce-hill.com/hill-noise)
51
+ def cos_noise(X,Y,amp,frecx,frecy,phase):
52
+ return amp*np.cos( frecx*X +frecy*Y + phase)
53
+ #return Amp*(np.cos( frecx*X) +np.cos(frecy*Y + phase))
54
+
55
+ # Generate a noise using superposition of cosinus
56
+ def cos_field(boxsizex,seed,scale,octaves,persistence,lacunarity,boxsizey=None):
57
+
58
+ if boxsizey==None: boxsizey=boxsizex
59
+ np.random.seed(seed=seed)
60
+
61
+ frec0 = 5.
62
+
63
+ x, y = np.linspace(0,boxsizex,num=boxsizex), np.linspace(0,boxsizey,num=boxsizey)
64
+ X, Y = np.meshgrid(x,y)
65
+
66
+ noise_tot = np.zeros((boxsizex,boxsizey))
67
+
68
+ for oct in range(octaves):
69
+ Amp, frecx, frecy, phase = np.random.random(), 2.*np.pi*frec0*random.uniform(-1.,1.), 2.*np.pi*frec0*random.uniform(-1.,1.), 2.*np.pi*np.random.random()
70
+ noise_tot += persistence**oct*cos_noise(X/scale,Y/scale,Amp,frecx*lacunarity**oct,frecy*lacunarity**oct,phase)
71
+
72
+ return noise_tot
73
+
74
+ # Generate a Perlin field with warping domain (see e.g. https://iquilezles.org/www/articles/warp/warp.htm)
75
+ def warped_perlin_field(boxsizex,seed,scale,octaves,persistence,lacunarity,amplitude=None,boxsizey=None):
76
+
77
+ if boxsizey==None: boxsizey=boxsizex
78
+ shape = (boxsizex,boxsizey)
79
+
80
+ if amplitude==None: amplitude = np.random.uniform(0.,30.)
81
+
82
+ field = np.zeros(shape)
83
+ for i in range(shape[0]):
84
+ for j in range(shape[1]):
85
+ vec = np.random.rand(2)
86
+ ii = noise.pnoise2(i/scale,j/scale,octaves=octaves,persistence=persistence,lacunarity=lacunarity,base=seed)
87
+ jj = noise.pnoise2(i/scale,j/scale,octaves=octaves,persistence=persistence,lacunarity=lacunarity,base=seed)
88
+ field[i,j] = noise.pnoise2(i/scale + amplitude*ii,j/scale + amplitude*jj,octaves=octaves,persistence=persistence,lacunarity=lacunarity,base=seed)
89
+ return field
90
+
91
+ # Embedding of covariance function on a [0,R]^2 grid for fractional Brownian field
92
+ # From https://gist.github.com/radarsat1/6f8b9b50d1ecd2546d8a765e8a144631
93
+ def rho(x,y,R,alpha):
94
+
95
+ if alpha <= 1.5:
96
+ # alpha=2*H, where H is the Hurst parameter
97
+ beta = 0
98
+ c2 = alpha/2
99
+ c0 = 1-alpha/2
100
+ else:
101
+ # parameters ensure piecewise function twice differentiable
102
+ beta = alpha*(2-alpha)/(3*R*(R**2-1))
103
+ c2 = (alpha-beta*(R-1)**2*(R+2))/2
104
+ c0 = beta*(R-1)**3+1-c2
105
+
106
+ # create continuous isotropic function
107
+ r = np.sqrt((x[0]-y[0])**2+(x[1]-y[1])**2)
108
+ if r<=1:
109
+ out=c0-r**alpha+c2*r**2
110
+ elif r<=R:
111
+ out=beta*(R-r)**3/r
112
+ else:
113
+ out=0
114
+
115
+ return out, c0, c2
116
+
117
+ # Fractional Brownian surface
118
+ # The main control is the Hurst parameter: H should be between 0 and 1, where 0 is very noisy, and 1 is smoother.
119
+ # From https://gist.github.com/radarsat1/6f8b9b50d1ecd2546d8a765e8a144631
120
+ def brownian_surface(boxsizex, H=0.8):
121
+ N = 2*boxsizex
122
+ R = 2 # [0,R]^2 grid, may have to extract only [0,R/2]^2
123
+
124
+ # size of grid is m*n; covariance matrix is m^2*n^2
125
+ M = N
126
+
127
+ # create grid for field
128
+ tx = np.linspace(0, R, M)
129
+ ty = np.linspace(0, R, N)
130
+ rows = np.zeros((M,N))
131
+
132
+
133
+ for i in range(N):
134
+ for j in range(M):
135
+ # rows of blocks of cov matrix
136
+ rows[j,i] = rho([tx[i],ty[j]],
137
+ [tx[0],ty[0]],
138
+ R, 2*H)[0]
139
+
140
+ BlkCirc_row = np.vstack(
141
+ [np.hstack([rows, rows[:,-1:1:-1]]),
142
+ np.hstack([rows[-1:1:-1,:], rows[-1:1:-1, -1:1:-1]])])
143
+
144
+ # compute eigen-values
145
+ lam = np.real(np.fft.fft2(BlkCirc_row))/(4*(M-1)*(N-1))
146
+ lam = np.sqrt(lam)
147
+
148
+ # generate field with covariance given by block circular matrix
149
+ Z = np.vectorize(complex)(np.random.randn(2*(M-1), 2*(M-1)),
150
+ np.random.randn(2*(M-1), 2*(M-1)))
151
+ F = np.fft.fft2(lam*Z)
152
+ F = F[:M, :N] # extract sub-block with desired covariance
153
+
154
+ out,c0,c2 = rho([0,0],[0,0],R,2*H)
155
+
156
+ field1 = np.real(F) # two independent fields
157
+ #field2 = np.imag(F)
158
+ #field1 = field1 - field1[0,0] # set field zero at origin
159
+ #field2 = field2 - field2[0,0] # set field zero at origin
160
+
161
+ # make correction for embedding with a term c2*r^2
162
+ field1 = field1 + np.kron(np.array([ty]).T * np.random.randn(), np.array([tx]) * np.random.randn())*np.sqrt(2*c2)
163
+ #field2 = field2 + np.kron(np.array([ty]).T * np.random.randn(), np.array([tx]) * np.random.randn())*np.sqrt(2*c2)
164
+ #X,Y = np.meshgrid(tx,ty)
165
+
166
+ field1 = field1[:N//2, :M//2]
167
+ #field2 = field2[:N//2, :M//2]
168
+ return field1
source/maps.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #---------------------------
2
+ # Field generator module
3
+ # PabloVD
4
+ # Started: 11/5/20
5
+ #---------------------------
6
+
7
+ import numpy as np
8
+ from scipy import interpolate, ndimage
9
+ from source.fields import gaussian_field, perlin_field, warped_perlin_field, brownian_surface, cos_field
10
+
11
+ # Filter the field with a gaussian window
12
+ def smooth_field(field,sigmagauss,gridsize=None):
13
+
14
+ if gridsize==None: gridsize=field.shape[0]
15
+
16
+ x, y = np.linspace(0,field.shape[0],num=field.shape[0]), np.linspace(0,field.shape[1],num=field.shape[1])
17
+
18
+ # Interpolation
19
+ f = interpolate.interp2d(x,y,field,kind="linear")
20
+
21
+ qx = np.linspace(x[0],x[-1], num = gridsize)
22
+ qy = np.linspace(y[0],y[-1], num = gridsize)
23
+
24
+ # Filtering
25
+ smooth = ndimage.filters.gaussian_filter(f(qx,qy),sigmagauss)
26
+ return smooth
27
+
28
+ # Remove regions below sea level
29
+ def mainland(field,threshold):
30
+ for i, row in enumerate(field):
31
+ for j, el in enumerate(row):
32
+ if el<threshold: field[i,j]=0.
33
+ return field
34
+
35
+ # Normalize the values of the field between 0 and 1
36
+ def normalize_field(field):
37
+ min, max = np.amin(field), np.amax(field)
38
+ newfield = (field-min)/(max-min)
39
+ return newfield
40
+
41
+ # A gaussian function
42
+ def central_gaussian(x,y,x_c,y_c,sig):
43
+ return np.exp(-((x-x_c)**2.+(y-y_c)**2.)/2./sig**2.)
44
+
45
+ # Multiply the field by a gaussian mask to ensure an island in the center of the image
46
+ def masked_field(field,sig=None):
47
+ a, b = field.shape[0], field.shape[1]
48
+ if sig==None: sig = a/2.
49
+ x, y = np.linspace(0,a-1,num=a), np.linspace(0,b-1,num=b)
50
+ X, Y = np.meshgrid(x,y)
51
+ mask = central_gaussian(X,Y,a/2,b/2,sig)
52
+ field = field*mask
53
+ return field
54
+
55
+ # Generate a map of islands applying different processes:
56
+ # 1. Generate a random field, either gaussian or perlin
57
+ # 2. Normalize the field between 0 and 1
58
+ # 3. Smooth the field with a gaussian filter
59
+ # 4. Retain only the mainland above a certain threshold
60
+ def generate_map(kind_noise,boxsize,llavor,params,sigma,threshold,boxsizey=None,make_island=0):
61
+
62
+ if boxsizey==None: boxsizey=boxsize
63
+ np.random.seed(seed=llavor)
64
+
65
+ if kind_noise=="gauss":
66
+ indexlaw = params
67
+ field = gaussian_field(boxsize,llavor,indexlaw)
68
+ elif kind_noise=="perlin":
69
+ scale,octaves,persistence,lacunarity,boxsizey = params
70
+ field = perlin_field(boxsize,llavor,scale,octaves,persistence,lacunarity,boxsizey=boxsizey)
71
+ #field = perlin_field(boxsize,llavor,*params)
72
+ elif kind_noise=="warped_perlin":
73
+ scale,octaves,persistence,lacunarity,boxsizey = params
74
+ field = warped_perlin_field(boxsize,llavor,scale,octaves,persistence,lacunarity,boxsizey=boxsizey)
75
+ elif kind_noise=="fbm":
76
+ hurst = params
77
+ field = brownian_surface(boxsize, H=hurst)
78
+ elif kind_noise=="cos":
79
+ scale,octaves,persistence,lacunarity,boxsizey = params
80
+ field = cos_field(boxsize,llavor,scale,octaves,persistence,lacunarity,boxsizey=boxsizey)
81
+ else:
82
+ print("Kind of noise not valid.")
83
+ return np.zeros((boxsize,boxsizey))
84
+
85
+ field = normalize_field(field)
86
+ if make_island:
87
+ field = masked_field(field)
88
+ field = smooth_field(field,sigma,gridsize=2*boxsize)
89
+ field = mainland(field,threshold)
90
+
91
+ return field
source/visualization_tools.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #---------------------------
2
+ # Tools for visualization
3
+ # PabloVD
4
+ # Started: 11/5/20
5
+ #---------------------------
6
+
7
+ import matplotlib.pyplot as plt
8
+ from source.maps import generate_map
9
+
10
+ # Modify gist_earth color map to have a dark blue as the minimum value, instead of black
11
+ def modified_gist_earth():
12
+
13
+ cmap = plt.get_cmap("gist_earth")
14
+ # Color maps defined for 256 values
15
+ num = 256
16
+ # Location of the color I want to be the lowest value
17
+ ind_blue = 5 #
18
+
19
+ blue_list = []
20
+ for i in range(ind_blue):
21
+ blue_list.append(cmap(ind_blue))
22
+
23
+ newcmap = cmap.from_list('modified_gist_earth',blue_list+list(map(cmap,range(ind_blue,num))), N=num)
24
+ return newcmap
25
+
26
+ # Creates a random map
27
+ def single_map(kind_noise,boxsize,llavor,params,sigma,threshold,make_island=0,cmap=modified_gist_earth(),axissize=6):
28
+
29
+ figsize = (axissize,axissize)
30
+ fig, ax = plt.subplots(figsize=figsize)
31
+ margins = { # vvv margin in inches
32
+ "left" : 0.,
33
+ "bottom" : 0.,
34
+ "right" : 1.,
35
+ "top" : 1.}
36
+ fig.subplots_adjust(**margins)
37
+
38
+ field = generate_map(kind_noise,boxsize,llavor,params,sigma,threshold,make_island=make_island)
39
+ ax.imshow(field,vmin=0.,vmax=1.,cmap=cmap)
40
+
41
+ ax.set_axis_off()
42
+
43
+ return fig
44
+
45
+
46
+ # Create (num_plots)x(num_plots) different maps of random islands, with different random seeds
47
+ def plot_grid(kind_noise,boxsize,params,sigma,threshold,num_plots=3,make_island=0,cmap=modified_gist_earth()):
48
+
49
+ fig, axes = plt.subplots(num_plots,num_plots, figsize=(9.,9.))
50
+ fig.subplots_adjust(wspace = 0.1, hspace = 0.1)
51
+
52
+ llavor = 0
53
+
54
+ for axx in axes:
55
+ for ax in axx:
56
+
57
+ field = generate_map(kind_noise,boxsize,llavor,params,sigma,threshold,make_island=make_island)
58
+ ax.imshow(field,vmin=0.,vmax=1.,cmap=cmap)
59
+
60
+ ax.set_xlim([0,field.shape[0]])
61
+ ax.set_ylim([field.shape[0],0])
62
+ ax.set_axis_off()
63
+ llavor+=1
64
+
65
+ plt.axis('off')
66
+ fig.savefig("images/gridmap_noise_{:}_threshold_{:.1f}_sigma_{:.1f}.png".format(kind_noise,threshold,sigma), bbox_inches='tight')
67
+ #plt.close(fig)
68
+