2025, Nov 12 03:00
Smooth Background Field Generation in Python: Build a Correlated Base Field and Add Controlled White Noise
Learn how to generate smooth 2D fields in Python using an attractor-based base field and additive white noise. Control spatial correlation and global variation
When you fill a 2D grid with independent random values, you get a speckled texture with a lot of local variance and very little structure. That’s fine for white noise, but not for a background field that changes smoothly within neighborhoods while still showing global variation across the map. Below is a compact approach that keeps a high level of control over smoothness and large-scale patterns without introducing hard edges.
Problem setup
The straightforward way to populate a meshgrid is to sample each pixel independently. That produces a noisy surface with sharp local deviations and no spatial correlation.
import numpy as np
u = np.linspace(-2, 2, 100)
v = np.linspace(-2, 2, 100)
U, V = np.meshgrid(u, v)
W = np.random.rand(100, 100)
The result is a classic white noise texture: high-frequency changes dominate, and adjacent pixels don’t “agree” with each other. For a background radiation map–like surface, you typically want locally smooth behavior with broad, gradual changes across the domain.
Why the basic approach looks noisy
With independent sampling at each pixel, neighboring values have no relationship to one another, so the surface lacks spatial coherence. You see strong local variation but minimal structure at larger scales. If you want the field to vary slowly in small neighborhoods while retaining global variation when you zoom out, you need to introduce spatial correlation and then, if desired, layer a controllable amount of additive noise.
A controlled-field approach: base field + additive white noise
A simple way to inject structure is to define a smooth base field via a set of attractors (points that influence the grid), accumulate their effects across the map, and then add a small amount of white noise for fine-grained texture. This gives you direct control over both the large-scale shape and the local roughness.
The idea is straightforward. Randomly place attractors with positive or negative weights. For each pixel, sum the influence of all attractors using an inverse-distance-like function. The resulting field is smooth and globally varied. Finally, add a tunable amount of additive white noise to introduce mild local irregularities if needed.
Implementation
import numpy as np
import matplotlib.pyplot as plt
def synth_region(side: int, jitter_amp: float):
# Repeatable RNG
prng = np.random.default_rng(seed=14)
# Output field
field = np.empty((side, side), dtype=float)
# Number of attractors
k_anchors = int(side)
# Attractor coordinates
anchor_xy = prng.integers(0, side, (k_anchors, 2), endpoint=False)
# Attractor weights in [-1, 1]
anchor_gain = prng.random(size=k_anchors) * 2 - 1
# Accumulate attractor influence for each pixel
for ix in range(side):
for iy in range(side):
accum = 0.0
for pt, weight in zip(anchor_xy, anchor_gain):
accum += (np.linalg.norm(pt - (ix, iy)) + side ** 0.5) ** -1 * weight
field[ix, iy] = accum
# Additive white noise, scaled by jitter_amp
field += prng.random(size=(side, side)) * jitter_amp
plt.imshow(field)
plt.show()
# Examples with progressively more local texture
synth_region(100, 0.0)
synth_region(100, 0.01)
synth_region(100, 0.05)
This produces a smooth field driven by spatially distributed influences. The jitter_amp parameter controls how much high-frequency texture you add on top of the base structure. Setting it to zero emphasizes large-scale variation and smooth transitions; increasing it introduces more local roughness.
You can adjust the number of attractors, their positions, and their weights to shape the landscape. You can also alter the influence function to change how quickly the field decays with distance. These knobs make the method highly tunable while avoiding blocky transitions or hard edges.
Why this matters
When you need a 2D surface that “looks physical” rather than random—something that varies slowly over short distances yet shows meaningful changes at larger scales—controlling spatial correlation is key. Defining a base field and adding a small amount of additive white noise gives you predictable, repeatable behavior with minimal artifacts, and you can dial in exactly how smooth or varied the map should be.
If you prefer to start from a coarse layout and refine it, another path is to interpolate a sparse grid to a fine one, for example via scipy.interpolate.griddata. And if your goal is specifically more energy at low spatial frequencies, driving the spectrum that way can also produce strong global variation with limited local change.
Takeaways
Independent per-pixel sampling yields white noise and destroys local coherence. If you need a smooth background with global structure, build a correlated base field and, only if necessary, add a controlled amount of additive white noise. The attractor-based accumulation pattern shown above is a compact way to get there, and it remains easy to tune by changing attractor placement, weights, influence shape, and the final noise amplitude.
The article is based on a question from StackOverflow by Space cat 321 and an answer by BitsAreNumbersToo.