surface_params — ROUGHNESS measure op

• Data kinds: depth → table

• Call: import fullseye as fs; fs.ledger.surface_params(z, dx=1.0, dy=None, assume_filtered=False) (to call the implementation directly, import roughness; roughness.surface_params(z, dx=1.0, dy=None, assume_filtered=False); from the registry, opsroughness.get("surface_params"))

Usage

Areal roughness parameters Sa/Sq/Sp/Sv/Sz/Ssk/Sku/Sdq/Sdr (ISO 25178-2).

Arguments

z band-processed height field (form and waviness removed).

dx, dy sampling intervals. dy=None means square pixels.

assume_filtered True skips the band check. Default is False = check.

Returns a dict. Besides the 9 parameters, to keep the basis of the decision as numbers, it contains band_long_wave_fraction / band_form_ptv_over_sq / n_points / assume_filtered.

★ Why it rejects by default (measured)

A synthetic 512x512, dx=1 µm surface (self-affine PSD + machining marks λ=32 µm + deep scratches

x4 + waviness λ=256 µm + tilt 0.050/-0.025), evaluated with different procedures.

The ground truth is "scratches, machining marks and PSD cut at λc=80 µm":

procedure Sq Sq error Sa Ssk Sku

raw height field as is 8.3057 +1931.4 % 6.9089 +0.05 2.26

least-squares plane removed 0.7520 +83.9 % 0.5273 -2.04 10.21

plane + λc=80µm high-pass 0.4104 +0.4 % 0.2501 -2.88 18.76

ground truth 0.4089 — 0.2502 -2.80 18.52

Calling the raw rms Sq gives 20.3 times. Ssk goes from -2.80 to +0.05, losing even its sign, and Sku goes from 18.5 to 2.26 (below the Gaussian 3). When waviness and

tilt dominate the distribution, the information of the deep scratches disappears without a trace.

Removing only the plane still leaves it 1.8 times off. There is no escape route of "which parameter

is safe" — numbers produced without declaring the band cannot be rescued by the choice of

parameter.

★ What the check does, and the basis of the thresholds (measured)

2 pieces of evidence. Long-wave fraction = share of the variance carried by waves longer than 1/4 of the evaluation length.

Form PTV/Sq = peak-to-valley of the least-squares plane itself divided by the rms.

surface long-wave form PTV/Sq verdict

raw height field 0.9714 4.62 reject

least-squares plane removed only 0.4415 0.00 reject

waviness only left (another surface) 0.5539 0.15 reject

plane + λc=80µm high-pass 0.0086 0.00 pass

ground truth (λc=80µm) 0.0042 0.00 pass

pure sine λ=32 (256 square) 0.0038 0.03 pass

Does the margin on the pass side hold however far λc is raised? — confirming that legitimate use is not wrongly rejected matters more, so λc was varied and measured

(512², surface containing machining marks + 1 scratch):

λc 16 32 64 80 128 170 256(=L/2)

long-wave 0.0005 0.0011 0.0044 0.0085 0.0306 0.0563 0.1056

Even with λc raised to half the evaluation length it only rises to 0.106.

The smallest surface to be rejected is at 0.4415. The threshold 0.25 is placed in between (2.4 times above the pass side,

1.8 times below the reject side). Form PTV/Sq, for its part, on band-processed surfaces is

only 0.001 to 0.03 measured, and on surfaces with tilt left it is 3.46 / 4.62.

The threshold 1.0 lies in between (33 times above the pass side, 3.5 times below the reject side).

These 2 are combined with or — a surface with no tilt but with waviness left

("waviness only left" in the table above) is not caught by form PTV; only the long-wave fraction

fires. Either one alone leaves a hole.

Still, so as not to hand the decision entirely to the machine, it can be explicitly switched off with assume_filtered=True

(it never switches off silently). The evidence values themselves are also returned as

band_long_wave_fraction / band_form_ptv_over_sq.

★ Pitfall (a) — Sz measures not "a property of the surface" but "how long you looked"

Keeping the same sampling interval (1 µm) and only widening the evaluation window, Sz does not level off but

increases monotonically (measured, surface with the scratches removed):

window 32² 64² 128² 256² 512²

windows 256 64 16 4 1

mean Sz 0.7112 0.8011 0.8693 0.9248 1.0058

meas/pred 0.5204 0.5295 0.5314 0.5287 0.5420

+41.4 % for 256 times the area. The prediction is the Gaussian extreme value 2√(2 ln M) · Sq.

The ratio is nearly constant regardless of the window (0.520 to 0.542, ±2.0 % around the mean), so

the shape of the growth (logarithm of the area) is right, but the absolute value is off by 1.9 times —

because the surface is correlated and the number of independent samples is smaller than the point count M.

Since it never levels off, no "true Sz" exists. When reporting Sz, always state the window size and the number of windows.

★ Pitfall (b) — the most robust against the sampling interval is not Sa but Sq

The same surface decimated by point sampling and evaluated each time with the same procedure (tilt removal → λc=80 µm high-pass);

relative error against the ground truth (measured):

dx Sa Sq Sz Ssk Sku

1 µm -0.0 % +0.4 % +0.2 % +2.8 % +1.3 %

2 µm -0.1 % +0.4 % -0.2 % +3.0 % +1.4 %

4 µm -0.3 % +0.4 % -1.3 % +3.4 % +1.7 %

8 µm -3.4 % +0.6 % -19.8 % +2.3 % -3.9 %

16 µm +23.5 % +1.6 % -29.1 % -38.4 % -40.8 %

The first to exceed 10 % is Sz at dx=8 µm, then Sa / Ssk / Sku at dx=16 µm;

Sq never exceeds it (max 1.6 %). Sq survives because aliasing

only folds energy back and preserves the 2nd moment. Sa moves when the shape of the distribution

changes, and Sz is an extreme value decided by "whether the bottom of a scratch happens to land on a sample point".

At dx=8 µm, Sa passes at ±5 % while Sz fails — on the same data,

the conclusion flips depending only on which number you looked at.

★ Pitfall (c) — Sdq / Sdr are affected by both the sampling interval and the units

Sdq is the rms of the gradient, Sdr the developed interfacial area ratio mean(√(1+|∇z|²)) - 1. Both

are computed with central differences, so for a sine of wavelength λ they come out, by the factor sinc(2 dx/λ),

too low (measured, λ=32 µm, amplitude 1, analytic Sdq=0.138840):

dx Sdq measured measured error sinc(2dx/λ) prediction

1 µm 0.138471 -0.27 % -0.64 %

2 µm 0.136212 -1.89 % -2.55 %

4 µm 0.126004 -9.25 % -9.97 %

8 µm 0.088388 -36.34 % -36.34 %

The coarser you measure, the smoother the surface looks. Sdr itself has the right form,

and with fine enough sampling it matches the analytic value within +0.116 % (measured with λ=32, dx=0.25).

If height and dx have different units, only this breaks silently (Sa/Sq

use only the height unit, so you cannot notice).

The outermost 1 row and 1 column use one-sided differences, so they are excluded from the evaluation.

fail-closed

• not 2-D / smaller than 8x8 / contains NaN or Inf.

• dx or dy non-positive or non-finite.

• does not look band-processed (when assume_filtered=False).

• all points at the same height (Sq=0, so Ssk/Sku are undefined).

Detailed usage guide

• surface_roughness family guide

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• poc_polish_wipe_measure — py -3.11 examples/poc_polish_wipe_measure.py

• poc_surface_roughness — py -3.11 examples/poc_surface_roughness.py

Ops the type connects to (they accept table as input)

—

Same category (measure)

profile_params · surface_psd


*Provenance: roughness.py — ROUGHNESS operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.