synthesis — Complete parameter reference#
Module: foscat.scat_cov, foscat.scat_cov2D, foscat.scat_cov1D
synthesis is the high-level entry point for field synthesis, denoising, and
texture generation. It wraps the full L-BFGS-B optimisation loop and handles
multi-resolution scheduling automatically.
result = scat_op.synthesis(
image_target,
reference = None,
nstep = 4,
seed = 1234,
Jmax = None,
edge = False,
to_gaussian = False,
use_variance = True,
synthesised_N = 1,
input_image = None,
grd_mask = None,
in_mask = None,
iso_ang = False,
fft_ang = False,
fft_nharm = 1,
fft_imaginary = True,
EVAL_FREQUENCY= 100,
NUM_EPOCHS = 300,
scat_cov_method = 'eval',
n_up = 0,
)
How synthesis works#
The optimiser searches for a field \(u\) whose scattering-covariance statistics
\(\Phi(u)\) match those of image_target:
where \(\sigma_k\) is the per-coefficient variance of image_target (when
use_variance=True). Gradients are computed via PyTorch autograd and the
minimiser is scipy.optimize.fmin_l_bfgs_b.
The multi-resolution schedule (nstep) runs this minimisation from coarse to
fine resolution, using each step’s result as the warm start for the next.
Parameters#
image_target (required)#
The reference field whose statistics are to be reproduced.
HEALPix: ndarray shape
(npix,)or(B, npix)withnpix = 12·nside²2D: ndarray shape
(H, W)or(B, H, W)1D: ndarray shape
(N,)or(B, N)
The input should be normalised (zero mean, unit variance is standard):
xnorm = (image - np.mean(image)) / np.std(image)
result = scat_op.synthesis(xnorm, ...)
nstep (default: 4)#
Number of resolution levels in the multi-resolution optimisation.
The algorithm builds a resolution pyramid by repeated 2× downsampling of
image_target. It then synthesises from coarsest to finest, using each result
as the initial condition for the next level.
|
Resolutions visited (2D example, 256×256 target) |
|---|---|
1 |
256×256 only (direct synthesis, slow convergence) |
2 |
128×128 → 256×256 |
3 |
64×64 → 128×128 → 256×256 |
4 |
32×32 → 64×64 → 128×128 → 256×256 |
Capped automatically if nstep > jmax - 1 (the map is too small for that
many downsampling steps). For a 256×256 2D map, the maximum useful nstep is
about 6.
# Multiscale synthesis — recommended for maps larger than ~64×64
result = scat_op.synthesis(xnorm, nstep=3, NUM_EPOCHS=300)
# Single-scale (faster, but worse convergence at large resolutions)
result = scat_op.synthesis(xnorm, nstep=1, NUM_EPOCHS=1000)
n_up (default: 0)#
Number of extra upsampling steps beyond the target size, keeping the same
Jmax (same wavelet scales).
With n_up=1, after completing synthesis at N×N, the algorithm continues at
2N×2N. The scattering statistics used are still those of the N×N target — only
the domain is larger. The result is a field that locally matches the statistics
of the original target, embedded in a larger canvas.
|
Output size (2D, N×N target) |
Comment |
|---|---|---|
0 |
N×N |
Standard synthesis |
1 |
2N×2N |
Same statistics, larger domain |
2 |
4N×4N |
Two extra levels |
The Jmax used during n_up steps is pinned to the value effective for the N×N
target (computed from its size and KERNELSZ), so wavelet filters and the norm
cache (P1_dic) remain consistent.
# Synthesise a 512×512 map whose statistics match a 256×256 target
result = scat_op.synthesis(xnorm_256, nstep=3, n_up=1, NUM_EPOCHS=300)
# result.shape == (512, 512)
NUM_EPOCHS (default: 300)#
Maximum number of L-BFGS-B iterations per resolution level.
The optimiser may stop earlier if the convergence criterion (factr) is reached.
A good rule of thumb: convergence is typically achieved in 100–500 iterations
per level for moderate-size maps.
The total wall-clock time scales as nstep × NUM_EPOCHS.
EVAL_FREQUENCY (default: 100)#
Print the current loss value every N L-BFGS-B iterations.
seed (default: 1234)#
Random seed for the Gaussian white noise used as the initial condition at the
coarsest resolution level (k=0).
Change seed to generate statistically independent realisations of the same
target:
results = [scat_op.synthesis(xnorm, seed=s, nstep=3) for s in range(10)]
Has no effect when input_image is provided (the initial condition is then
taken from input_image).
synthesised_N (default: 1)#
Number of independent synthetic maps to produce in a single run (batch synthesis).
All synthesised_N maps are optimised simultaneously with the same loss, which
amortises the per-iteration cost. The output has an extra leading dimension:
result = scat_op.synthesis(xnorm, synthesised_N=4, nstep=3)
# result.shape == (4, H, W) for 2D
When synthesised_N > 1 the reference statistics are computed as the
batch-mean of image_target (if batched) or from the single target map
replicated for all synthesised_N maps.
use_variance (default: True)#
Controls the loss weighting.
True(recommended): each coefficient is divided by its standard deviation estimated fromimage_target. This makes the loss scale-invariant across spatial scales — large-scale and small-scale coefficients contribute equally regardless of their dynamic range.\[\mathcal{L}(u) = \sum_k \frac{(\Phi(u)_k - \Phi(d)_k)^2}{\sigma_k^2}\]False: all coefficients are weighted equally. Dominated by the largest-amplitude statistics (usually the coarsest scales). Useful if you want to enforce exact coefficient values rather than normalised deviations.
Jmax (default: None)#
Maximum wavelet scale index included in the loss. When None, all scales
available for the map size are used.
Reduce Jmax to match only small-scale statistics (fast textures) while
leaving large-scale structure free:
# Use only scales j < 4 — ignore large-scale correlations
result = scat_op.synthesis(xnorm, Jmax=4, nstep=3)
During the multi-resolution schedule, Jmax at level k is automatically
decremented by 1 for each coarser level: Jmax_k = Jmax - (nstep - 1 - k).
edge (default: False)#
Whether the map has non-periodic boundaries (limited spatial domain).
False: assumes periodic / full-sphere boundary conditions. The convolutions wrap around the edges.True: activates an internal edge mask that down-weights pixels near the boundary at each resolution level. Use for rectangular images or partial-sky HEALPix patches where the field does not wrap.
Automatically set to True when in_mask is provided.
in_mask (default: None)#
A binary (or soft) mask that marks invalid pixels in the input data — pixels that should not contribute to the reference statistics.
Shape must match image_target. Pixels with in_mask = 0 are excluded from
the scattering-covariance computation at every scale level (the mask is
downsampled at each coarser level). Setting in_mask also enables edge=True
internally.
Use case: partial-sky CMB analysis, images with missing regions, survey footprints.
mask = np.ones_like(image_target)
mask[bad_pixels] = 0.0
result = scat_op.synthesis(xnorm, in_mask=mask, NUM_EPOCHS=300)
grd_mask (default: None)#
A binary mask controlling which pixels of the synthesised map are free to move.
Pixels where grd_mask = 0 are frozen to their values in image_target;
only pixels where grd_mask = 1 are updated by the optimiser.
Use case: inpainting (reconstruct missing regions while preserving observed pixels), or constrained synthesis where part of the field is fixed.
grd_mask = np.zeros_like(image_target)
grd_mask[missing_pixels] = 1.0 # only synthesise these pixels
result = scat_op.synthesis(xnorm, grd_mask=grd_mask, NUM_EPOCHS=500)
At each multi-resolution level the gradient mask is downsampled to match the current resolution.
reference (default: None)#
A second reference field for cross-scattering-covariance synthesis.
When provided, the loss matches the cross-statistics between the synthesised
map and a fixed second field (rather than the auto-statistics of
image_target):
where \(d_1\) = image_target, \(d_2\) = reference.
Use case: component separation, where the synthesised field must match a specific covariance structure with a known companion field.
result = scat_op.synthesis(
cmb_estimate,
reference=dust_template,
NUM_EPOCHS=300,
)
iso_ang (default: False)#
Whether to use isotropically averaged statistics in the loss.
False: the loss is computed on the full oriented statistics (S1, S2, S3, S3P, S4 with all orientation indices). The synthesised map can reproduce anisotropic structures (filaments, oriented textures).True: the statistics are collapsed to their rotationally invariant content viaiso_mean()before computing the loss:Stat
Shape before iso
Shape after iso
Reduction
S1, S2
(..., L)(...)simple mean over
LS3, S3P
(..., L, L)(..., L)mean over
l1at fixedl2-l1S4
(..., L, L, L)(..., L, L)mean over
l1at fixed(l2-l1, l3-l1)Use for fields that are statistically isotropic (no preferred direction). Reduces the number of constraints and accelerates convergence.
# Isotropic synthesis — suitable for CMB-like fields
result = scat_op.synthesis(xnorm, iso_ang=True, nstep=3)
Note
iso_ang and fft_ang should not be used together. iso_ang is the harder
reduction (mean only); fft_ang keeps angular variation and is the recommended
soft alternative.
fft_ang (default: False) / fft_nharm (default: 1) / fft_imaginary (default: True)#
A softer alternative to iso_ang that compresses the orientation axes to
their first Fourier harmonics instead of collapsing them to a single mean.
The three parameters work together:
Parameter |
Type |
Default |
Role |
|---|---|---|---|
|
bool |
|
Enable Fourier angular compression |
|
int |
|
Number of harmonics to keep beyond DC |
|
bool |
|
Keep both cos and sin components |
What fft_ang=True keeps (with fft_nharm=1, fft_imaginary=True)#
Three coefficients per projected axis (basis functions \(\phi_k\)):
Index k |
\(\phi_k(l)\) |
Physical meaning |
|---|---|---|
0 |
\(1/L\) |
DC — mean over the projected axis (= |
1 |
\(\cos(2\pi l/L)\) |
In-phase first harmonic |
2 |
\(\sin(2\pi l/L)\) |
Quadrature first harmonic |
The amplitude \(A_1 = \sqrt{c_1^2 + s_1^2}\) is rotation-invariant regardless
of how the image is oriented. This is why fft_imaginary=True is strongly recommended:
with cosine only (fft_imaginary=False) a field oriented at 90° gives \(c_1 \approx 0\)
even when strongly anisotropic.
Shapes and projection rules after fft_ang(nharm=1, imaginary=True) → nout = 3#
Statistic |
Before |
After |
Projection axis |
|---|---|---|---|
S1, S2 |
|
|
the single orientation axis |
S3, S3P |
|
|
l1 at fixed Δl = l2−l1 |
S4 |
|
|
l1 at fixed (Δl12, Δl13) |
For S3/S4 the projection is not a tensor product. The relative-orientation
axes (Δl) are preserved exactly as in iso_mean, and only the absolute orientation
axis l1 is projected:
The \(k=0\) coefficient equals iso_mean exactly (\(\phi_0 = 1/L\), so \(\sum_{l_1}(1/L)\cdot S3[\ldots] = \text{mean}\)).
Components \(k=1,2\) capture the angular modulation around that mean.
fft_ang vs iso_ang — choosing the right reduction#
|
|
|
|---|---|---|
S1/S2 output shape |
|
|
S3/S3P output shape |
|
|
S4 output shape |
|
|
Angular information kept |
mean only |
mean + angular modulation amplitude |
Number of constraints |
minimum |
moderate (+factor 3 on last axis) |
Suited for |
strictly isotropic fields |
fields with preferred but variable orientation |
Cost per iteration |
lowest |
slightly higher |
# fft_ang synthesis — preserves angular variation amplitude
result = scat_op.synthesis(xnorm, fft_ang=True, nstep=3, NUM_EPOCHS=300)
# Keep two harmonics (DC + first two angular modes):
result = scat_op.synthesis(xnorm, fft_ang=True, fft_nharm=2, nstep=3)
# With fft_imaginary=False only if the image orientation is known and fixed:
result = scat_op.synthesis(xnorm, fft_ang=True, fft_imaginary=False, nstep=3)
to_gaussian (default: False)#
Apply a Gaussianisation transform to image_target before synthesis, then
invert it at the end.
True: maps the histogram ofimage_targetto a standard Gaussian before computing target statistics. The final output is mapped back to the original histogram. Useful for highly non-Gaussian fields (log-normal dust emission, etc.) where the scattering statistics alone do not fully constrain the one-point distribution.False: the histogram of the synthesised map is controlled only by the scattering statistics (which capture some non-Gaussian content but are not guaranteed to reproduce the full marginal distribution).
input_image (default: None)#
A warm-start initial condition for the coarsest resolution level.
When None, the optimiser starts from Gaussian white noise (seeded by seed).
When provided, the field input_image is downsampled to each resolution level
and used as the initial guess at k=0.
Use case: iterative refinement, re-synthesis starting from a previous result, or injecting a prior.
# Refine a previous result
result_v1 = scat_op.synthesis(xnorm, nstep=3, NUM_EPOCHS=100)
result_v2 = scat_op.synthesis(xnorm, nstep=3, NUM_EPOCHS=500,
input_image=result_v1)
scat_cov_method (default: 'eval')#
Selects the internal method used to compute scattering covariances at each optimisation step.
'eval'(default): usesfunct.eval()withnorm='auto'. The normalisation factors are computed from the target image on the first call and cached; subsequent iterations reuse the same normalisation. Works for all geometry types (2D, HEALPix, 1D). Supports all orientation reductions:iso_ang,fft_ang.any other value (e.g.
'scattering_cov'): uses the directscattering_cov()path. 2D only. Statistics are computed without thenorm='auto'caching layer; normalisation is applied via theref_sigmamechanism insidescattering_covitself. Supportsiso_angandfft_ang(both with and withoutuse_variance).Use this path when you want to bypass the
evalnormalisation cache — for example, in cases where the cached norm from the target image is not appropriate for the synthesised image (non-stationary textures, masked domains with very different statistics).
|
|
|
|---|---|---|
Geometry |
2D, HEALPix, 1D |
2D only |
|
✓ |
✓ (native) |
|
✓ |
✗ — raises |
|
✓ |
✓ (ref recomputed at each resolution) |
Normalisation |
cached from target ( |
via |
|
✓ ( |
✓ ( |
Note
fft_ang=True is incompatible with scat_cov_method != 'eval'. The
scattering_cov path returns a flat concatenated tensor (all statistics
serialised into a 1-D vector) rather than a structured scat_cov object,
so the orientation-Fourier post-processing that fft_ang requires cannot
be applied. A ValueError is raised immediately if this combination is
attempted.
Return value#
If
synthesised_N == 1: ndarray with the same shape asimage_target(the leading batch dimension is squeezed).If
synthesised_N > 1: ndarray with a leading dimension of sizesynthesised_N.If
n_up > 0(2D only): the output has shape(2^n_up · H, 2^n_up · W).
Complete examples#
Minimal 2D synthesis#
import foscat.scat_cov2D as sc
import numpy as np
scat_op = sc.funct(NORIENT=4)
xnorm = (image - np.mean(image)) / np.std(image)
result = scat_op.synthesis(xnorm, seed=10, nstep=3, NUM_EPOCHS=300)
Batch synthesis with masking#
mask = np.ones_like(xnorm)
mask[invalid] = 0.0
results = scat_op.synthesis(
xnorm,
in_mask = mask,
synthesised_N = 4,
nstep = 3,
iso_ang = True,
NUM_EPOCHS = 500,
)
# results.shape == (4, H, W)
Upsampled synthesis (n_up)#
# Synthesise a 512×512 map matching the statistics of a 256×256 target
result = scat_op.synthesis(
xnorm_256,
nstep = 3,
n_up = 1, # one extra 2× upsampling after full resolution
NUM_EPOCHS = 300,
)
# result.shape == (512, 512)
Inpainting (frozen observed pixels)#
grd_mask = np.zeros_like(xnorm)
grd_mask[hole_pixels] = 1.0 # only these pixels move
result = scat_op.synthesis(
xnorm,
grd_mask = grd_mask,
nstep = 3,
edge = True,
NUM_EPOCHS = 500,
)
Cross-covariance synthesis (component separation)#
# Synthesise CMB-like field matching cross-statistics with a dust template
result = scat_op.synthesis(
cmb_estimate,
reference = dust_template,
nstep = 3,
iso_ang = True,
NUM_EPOCHS = 300,
)
Isotropic Gaussianised synthesis#
result = scat_op.synthesis(
image_target,
to_gaussian = True,
iso_ang = True,
nstep = 4,
NUM_EPOCHS = 500,
)