diff --git a/content/tutorials/noise/acm.csl b/content/tutorials/noise/acm.csl
new file mode 100644
index 0000000..50853ee
--- /dev/null
+++ b/content/tutorials/noise/acm.csl
@@ -0,0 +1,215 @@
+
+
diff --git a/content/tutorials/noise/images/noise_06.webp b/content/tutorials/noise/images/noise_06.webp
new file mode 100644
index 0000000..3f95904
Binary files /dev/null and b/content/tutorials/noise/images/noise_06.webp differ
diff --git a/content/tutorials/noise/images/noise_07.webp b/content/tutorials/noise/images/noise_07.webp
new file mode 100644
index 0000000..1bb2681
Binary files /dev/null and b/content/tutorials/noise/images/noise_07.webp differ
diff --git a/content/tutorials/noise/images/noise_08.webp b/content/tutorials/noise/images/noise_08.webp
new file mode 100644
index 0000000..929c8be
Binary files /dev/null and b/content/tutorials/noise/images/noise_08.webp differ
diff --git a/content/tutorials/noise/images/noise_09.webp b/content/tutorials/noise/images/noise_09.webp
new file mode 100644
index 0000000..a4dea3e
Binary files /dev/null and b/content/tutorials/noise/images/noise_09.webp differ
diff --git a/content/tutorials/noise/images/noise_10.webp b/content/tutorials/noise/images/noise_10.webp
new file mode 100644
index 0000000..5b82801
Binary files /dev/null and b/content/tutorials/noise/images/noise_10.webp differ
diff --git a/content/tutorials/noise/images/noise_11.webp b/content/tutorials/noise/images/noise_11.webp
new file mode 100644
index 0000000..b8d90fc
Binary files /dev/null and b/content/tutorials/noise/images/noise_11.webp differ
diff --git a/content/tutorials/noise/noise.bib b/content/tutorials/noise/noise.bib
new file mode 100644
index 0000000..63edeb2
--- /dev/null
+++ b/content/tutorials/noise/noise.bib
@@ -0,0 +1,182 @@
+@incollection{Gonzalez:2015,
+title = {Fractal {Brownian} {Motion}},
+url = {https://thebookofshaders.com/13/},
+booktitle = {The {Book} of {Shaders}},
+author = {Gonzalez Vivo, Patricio and Lowe, Jen},
+year = {2015},
+}
+
+@article{Harris:2020,
+title = {Array programming with {NumPy}},
+author = {
+Charles R. Harris and
+K. Jarrod Millman and
+St{\'{e}}fan J. van der Walt and
+Ralf Gommers and
+Pauli Virtanen and
+David Cournapeau and
+Eric Wieser and
+Julian Taylor and
+Sebastian Berg and
+Nathaniel J. Smith and
+Robert Kern and
+Matti Picus and
+Stephan Hoyer and
+Marten H. van Kerkwijk and
+Matthew Brett and
+Allan Haldane and
+Jaime Fern{\'{a}}ndez del R{\'{i}}o and
+Mark Wiebe and
+Pearu Peterson and
+Pierre G{\'{e}}rard-Marchant and
+Kevin Sheppard and
+Tyler Reddy and
+Warren Weckesser and
+Hameer Abbasi and
+Christoph Gohlke and
+Travis E. Oliph{\hyphenate}ant
+},
+year = {2020},
+month = {9},
+journal = {Nature},
+volume = {585},
+number = {7825},
+pages = {357--362},
+doi = {10.1038/s41586-020-2649-2},
+publisher = {Springer Science and Business Media {LLC}}
+}
+
+@article{Mandelbrot:1968,
+author = {Benoît B. Mandelbrot and John W. Van Ness},
+journal = {SIAM Review},
+number = {4},
+pages = {422--437},
+publisher = {Society for Industrial and Applied Mathematics},
+title = {Fractional Brownian Motions, Fractional Noises and Applications},
+issn = {00361445},
+url = {http://www.jstor.org/stable/2027184},
+volume = {10},
+year = {1968}
+}
+
+@book{Mandelbrot:1983,
+title = {The Fractal Geometry of Nature},
+isbn = {978-0-7167-1186-5},
+publisher = {W.H. Freeman},
+author = {Mandelbrot, Benoît B.},
+year = {1983},
+}
+
+@article{Musgrave:1989,
+title = {The synthesis and rendering of eroded fractal terrains},
+volume = {23},
+issn = {0097-8930},
+doi = {10.1145/74334.74337},
+number = {3},
+journal = {ACM SIGGRAPH Computer Graphics},
+publisher = {Association for Computing Machinery},
+author = {Musgrave, Forest Kenton and Kolb, Craig E. and Mace, Robert S.},
+month = {7},
+year = {1989},
+pages = {41--50},
+}
+
+@incollection{Musgrave:2003,
+address = {San Francisco},
+edition = {Third Edition},
+series = {The {Morgan Kaufmann} Series in Computer Graphics},
+title = {Procedural Fractal Terrains},
+isbn = {978-1-55860-848-1},
+doi = {10.1016/B978-155860848-1/50045-0},
+booktitle = {Texturing and Modeling},
+publisher = {Morgan Kaufmann},
+author = {Musgrave, Forest Kenton},
+editor = {
+Ebert, David S. and
+Musgrave, Forest Kenton and
+Peachey, Darwyn and
+Perlin, Ken and
+Worley, Steven and
+Mark, William R. and
+Hart, John C.
+},
+year = {2003},
+pages = {489--506},
+}
+
+@incollection{Musgrave:2004,
+address = {New York, NY, USA},
+series = {{SIGGRAPH} '04},
+title = {Fractal Terrains and Fractal Planets},
+isbn = {978-1-4503-7801-7},
+doi = {10.1145/1103900.1103932},
+booktitle = {The Elements of Nature: Interactive and Realistic Techniques},
+publisher = {Association for Computing Machinery},
+author = {Musgrave, Forest Kenton},
+editor = {
+Deusen, Oliver and
+Ebert, David S. and
+Fedkiw, Ron and
+Musgrave, Forest Kenton and
+Prusinkiewicz, Przemyslaw and
+Roble, Doug and
+Stam, Jos and
+Tessendorf, Jerry
+},
+year = {2004},
+pages = {7.1--7.45},
+}
+
+@software{NumPy,
+title = {NumPy},
+author = {{NumPy Developers}},
+year = {2023},
+license = {BSD 3-Clause License},
+url = {https://numpy.org},
+version = {1.25.0},
+repository= {https://github.com/numpy/numpy}
+}
+
+@article{Perlin:1985,
+title = {An Image Synthesizer},
+volume = {19},
+issn = {0097-8930},
+doi = {10.1145/325165.325247},
+number = {3},
+journal = {ACM SIGGRAPH Computer Graphics},
+publisher = {Association for Computing Machinery},
+author = {Perlin, Ken},
+month = {7},
+year = {1985},
+pages = {287--296},
+}
+
+@misc{Quilez:2019,
+author = {Quilez, Inigo},
+title = {Fractional Brownian Motion},
+url = {https://iquilezles.org/articles/fbm/},
+urldate = {2026-04-26},
+year = {2019}
+}
+
+@software{Seaborn,
+title = {{Seaborn: statistical data visualization}},
+author = {Michael L. Waskom},
+year = {2022},
+license = {BSD 3-Clause License},
+url = {https://seaborn.pydata.org},
+version = {0.12.2},
+repository= {https://github.com/mwaskom/seaborn}
+}
+
+@article{Waskom:2021,
+doi = {10.21105/joss.03021},
+year = {2021},
+publisher = {The Open Journal},
+volume = {6},
+number = {60},
+pages = {3021},
+author = {Michael L. Waskom},
+title = {seaborn: statistical data visualization},
+journal = {Journal of Open Source Software}
+}
diff --git a/content/tutorials/noise/noise.qmd b/content/tutorials/noise/noise.qmd
index fe51def..156e09d 100644
--- a/content/tutorials/noise/noise.qmd
+++ b/content/tutorials/noise/noise.qmd
@@ -6,13 +6,27 @@ date-modified: today
categories: [noise, terrain, raster, map algebra, python, intermediate]
description: Learn how to generate procedural noise using Python scripting in GRASS.
image: images/noise_01.webp
+bibliography: noise.bib
+csl: acm.csl
links:
+ notebooks: "[Get started with GRASS & Python in Jupyter Notebooks](https://grass-tutorials.osgeo.org/content/tutorials/get_started/fast_track_grass_and_python.html)"
g_region: "[g.region](https://grass.osgeo.org/grass-stable/manuals/g.region.html)"
g_extension: "[g.extension](https://grass.osgeo.org/grass-stable/manuals/g.extension.html)"
r_random_walk: "[r.random.walk](https://grass.osgeo.org/grass-stable/manuals/addons/r.random.walk.html)"
r_mapcalc: "[r.mapcalc](https://grass.osgeo.org/grass-stable/manuals/r.mapcalc.html)"
r_neighbors: "[r.neighbors](https://grass.osgeo.org/grass-stable/manuals/r.neighbors.html)"
r_surf_fractal: "[r.surf.fractal](https://grass.osgeo.org/grass-stable/manuals/r.surf.fractal.html)"
+ r_surf_gauss: "[r.surf.gauss](https://grass.osgeo.org/grass-stable/manuals/r.surf.gauss.html)"
+ np_rng: "[numpy.random.default_rng](https://numpy.org/doc/stable/reference/random/generator.html)"
+ np_standard_normal: "[numpy.random.Generator.standard_normal](https://numpy.org/doc/stable/reference/random/generated/numpy.random.Generator.standard_normal.html)"
+ np_meshgrid: "[numpy.meshgrid](https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html)"
+ np_hypot: "[numpy.hypot](https://numpy.org/doc/stable/reference/generated/numpy.hypot.html)"
+ np_fft_rfft: "[numpy.fft.rfft](https://numpy.org/doc/stable/reference/generated/numpy.fft.rfft.html)"
+ np_fft_fftfreq: "[numpy.fft.fftfreq](https://numpy.org/doc/stable/reference/generated/numpy.fft.fftfreq.html)"
+ np_fft_irfft: "[numpy.fft.irfft](https://numpy.org/doc/stable/reference/generated/numpy.fft.irfft.html)"
+ np_fft_fft2: "[numpy.fft.fft2](https://numpy.org/doc/stable/reference/generated/numpy.fft.fft2.html)"
+ np_fft_ifft2: "[numpy.fft.ifft2](https://numpy.org/doc/stable/reference/generated/numpy.fft.ifft2.html)"
+ sns_lineplot: "[seaborn.lineplot](https://seaborn.pydata.org/generated/seaborn.lineplot.html)"
format:
ipynb: default
html:
@@ -26,7 +40,7 @@ execute:
jupyter: python3
---
-
+
This tutorial is an introduction to generating procedural noise
with Python scripting in GRASS.
@@ -45,12 +59,12 @@ This tutorial covers:
* Ridge noise
* Fractal noise
* Multifractal noise
+* Colored noise
::: {.callout-note title="Computational notebook"}
-This tutorial can be run as a
-[computational notebook](https://grass-tutorials.osgeo.org/content/tutorials/noise/noise.ipynb).
+This tutorial can be run as a computational notebook.
Learn how to work with notebooks in the tutorial
-[Get started with GRASS & Python in Jupyter Notebooks](../get_started/fast_track_grass_and_python.qmd).
+{{< meta links.notebooks >}}.
:::
# Setup
@@ -92,7 +106,12 @@ gs.create_project(path=temporary.name, name="xy")
session = gj.init(Path(temporary.name, "xy"))
# Set region
-gs.run_command("g.region", n=200, e=800, s=0, w=0, res=1)
+north = 200
+east = 800
+south = 0
+west = 0
+res = 1
+gs.run_command("g.region", n=north, e=east, s=south, w=west, res=res)
```
# Random Walks
@@ -168,7 +187,7 @@ m.show()
The original form of gradient noise - Perlin noise -
is generated by interpolating between
random gradient vectors on a grid and their offsets
-([Perlin 1985](https://doi.org/10.1145/325165.325247)).
+[@Perlin:1985].
We will use map algebra to generate
a generalized form of gradient noise
by progressively smoothing a random raster surface.
@@ -337,6 +356,7 @@ m.show()

# Fractal Noise
+
Fractional Brownian motion
is a random walk through space
that is contingent on past steps
@@ -347,9 +367,7 @@ by accumulating octaves, i.e. iterations,
of procedural noise
with incremental changes
in frequency and amplitude
-([Musgrave 2003](https://doi.org/10.1016/B978-155860848-1/50045-0),
-[Vivo & Lowe 2015](https://thebookofshaders.com/13/),
-[Quilez 2019](https://iquilezles.org/articles/fbm/)).
+[@Musgrave:2003; @Gonzalez:2015; @Quilez:2019].
First, use the raster map calculator
{{< meta links.r_mapcalc >}}
to create a constant surface.
@@ -428,8 +446,7 @@ m.show()
Multifractal noise can be generated
by more complex incremental accumulations
of procedural noise
-([Musgrave et al. 1989](https://doi.org/10.1145/325165.325247),
-[Musgrave 2004](https://doi.org/10.1145/1103900.1103932)).
+[@Musgrave:1989; @Musgrave:2004].
In this example we will model multifractal Brownian motion
by accumulating octaves of turbulent fractal gradient noise.
First, use the raster map calculator
@@ -516,18 +533,185 @@ m.show()

-# References
+# Colored Noise
+
+In signal processing, noise is described by its color
+-- by the spectral density of the signal.
+These fractional noises take the form
+$1 / f^\alpha$ where $0 < \alpha < 2$
+[@Mandelbrot:1968].
+White noise has a constant spectral density and sounds indistinct.
+Examples include the radio and television static.
+Pink noise $1 / f$ has a spectral density
+that is inversely proportional to its frequency
+and thus has equivalent energy in each octave.
+This gives pink noise the self similar properties
+needed for modeling fluctuating phenomena
+like clouds and terrain.
+In this example we will generate white noise
+simply by plotting a standard normal distribution.
+Then we will generate fractional noise
+by taking the Fourier transform of white noise,
+dividing this by frequency $f^\alpha$,
+and then taking its inverse Fourier transform.
+Try different values of $\alpha$
+to generate other shades of colored noise!
+
+First we will plot the waveforms
+of one-dimensional white and pink noise
+with the [Seaborn](https://seaborn.pydata.org/)
+data visualization library [@Waskom:2021; @Seaborn].
+To generate one-dimensional white noise,
+instantiate Numpy's random number generator
+{{< meta links.np_rng >}},
+sample from a standard normal distribution with
+{{< meta links.np_standard_normal >}},
+and plot with
+{{< meta links.sns_lineplot >}}.
+
+```{python}
+# Import libraries
+import numpy as np
+import seaborn as sns
+
+# Set parameters
+seed = 0
+n = 512 # number of samples
+
+# Generate white noise
+rng = np.random.default_rng(seed)
+noise = rng.standard_normal(n)
+
+# Plot waveform
+sns.lineplot(data=noise, color=(0.26, 0.004, 0.33))
+```
+
+
+
+To generate one-dimensional pink noise,
+first compute the Fast Fourier Transform of the white noise with
+{{< meta links.np_fft_rfft >}},
+then scale the transformed noise by $1 / f^\alpha$,
+compute its inverse Fast Fourier Transform with
+{{< meta links.np_fft_irfft >}},
+and plot with
+{{< meta links.sns_lineplot >}}.
+
+```{python}
+# Set parameters
+alpha = 1.0 # where white=0, pink=1, red=1.5, brownian=2
+
+# Generate pink noise
+noise = np.fft.rfft(noise, n)
+frequency = np.arange(1, noise.shape[-1]+1)
+noise /= frequency ** alpha
+noise = np.fft.irfft(noise, n)
+
+# Plot waveform
+sns.lineplot(data=noise, color=(0.26, 0.004, 0.33))
+```
+
+
+
+Now we will generate two-dimensional fractional noise
+using GRASS and NumPy [@Harris:2020; @NumPy].
+First, generate a random surface
+with a normal distribution using
+{{< meta links.r_surf_gauss >}}
+and convert this raster into an array.
+Next generate a grid of frequencies.
+To do this, calculate sample frequencies
+for each dimension with
+{{< meta links.np_fft_fftfreq >}}
+register them in a grid with
+{{< meta links.np_meshgrid >}},
+calculate their radial distance
+from the origin with
+{{< meta links.np_hypot >}}
+and then mask out zeroes
+to avoid division by zero.
+Compute the two-dimensional Fast Fourier Transform
+of the white noise with
+{{< meta links.np_fft_fft2 >}}.
+Divide this transformed white noise
+by the frequency raised to scaling exponent $\alpha$.
+For fractional noise with the form
+$1 / f^\alpha$,
+$\alpha$ is a scaling exponent where
+zero generates white noise,
+one generates pink noise, and
+two generates fractional noise.
+Compute the two-dimensional
+inverse Fast Fourier Transform of the noise with
+{{< meta links.np_fft_ifft2 >}}
+and normalize the noise.
+Now that we have generated fractional noise with NumPy,
+we can write the array as a raster in GRASS.
+
+```{python}
+# Import libraries
+from grass.script import array as garray
+import numpy as np
-Gonzalez Vivo, Patricio, and Jen Lowe. 2015. “Fractal Brownian Motion.” In The Book of Shaders. .
+# Set parameters
+mean = 0
+sigma = 1.0
+seed = 0
+alpha = 1.5 # where white=0, pink=1, red=1.5, brownian=2
+
+# Generate random surface
+gs.run_command(
+ "r.surf.gauss",
+ output="noise",
+ mean=mean,
+ sigma=sigma,
+ seed=seed,
+ overwrite=True
+ )
-Mandelbrot, Benoît B. 1983. The Fractal Geometry of Nature. W.H. Freeman.
+# Convert to array
+noise = garray.array(mapname="noise")
+
+# Generate frequency
+u = np.fft.fftfreq(east)
+v = np.fft.fftfreq(north)
+u, v = np.meshgrid(u, v)
+frequency = np.hypot(u, v)
+mask = frequency != 0
+frequency[~mask] = 1.0
+
+# Calculate noise
+noise = np.fft.fft2(noise)
+scaling = frequency ** alpha
+noise = noise / scaling
+noise = np.fft.ifft2(noise).real
+
+# Normalize
+noise = noise / np.linalg.norm(noise)
+minima, maxima = np.min(noise), np.max(noise)
+noise = (noise - minima) / (maxima - minima)
+
+# Convert to raster
+raster = garray.array()
+for y in range(raster.shape[0]):
+ for x in range(raster.shape[1]):
+ raster[y, x] = noise[y, x]
+raster.write(mapname="noise", overwrite=True)
+
+# Set color table
+gs.run_command("r.colors", map="noise", color="viridis")
-Musgrave, Forest K., Craig E. Kolb, and Robert S. Mace. 1989. “The Synthesis and Rendering of Eroded Fractal Terrains.” ACM SIGGRAPH Computer Graphics (New York, NY, USA) 23 (3): 41–50. .
+# Visualize
+m = gj.Map(width=800)
+m.d_rast(map="noise")
+m.d_legend(raster="noise", color="white", digits=1, fontsize=10, at=(5, 95, 1, 3))
+m.show()
+```
-Musgrave, Forest Kenton. 2003. “Procedural Fractal Terrains.” In Texturing and Modeling, Third Edition, edited by David S. Ebert, Forest Kenton Musgrave, Darwyn Peachey, et al. The Morgan Kaufmann Series in Computer Graphics. Morgan Kaufmann. .
+
-Musgrave, Forest Kenton. 2004. “Fractal Terrains and Fractal Planets.” In The Elements of Nature: Interactive and Realistic Techniques, edited by Oliver Deusen, David S. Ebert, Ron Fedkiw, et al. SIGGRAPH ’04. Association for Computing Machinery. .
+
-Perlin, Ken. 1985. “An Image Synthesizer.” ACM SIGGRAPH Computer Graphics (New York, NY, USA) 19 (3): 287–96. .
+
-Quilez, Inigo. 2019. “Fractional Brownian Motion.” .
\ No newline at end of file
+
\ No newline at end of file