Thanks to visit codestin.com
Credit goes to github.com

Skip to content

moments_normalized: clarify the normalization scale for anisotropic spacing #8315

Description

@rastagan-git

Description

This follows up on #8312, but separates the remaining normalization question
from that issue's expectation of exact equality across different raster
resolutions.

On current main (457ef06), moments_central applies each axis's spacing to
the coordinates, while moments_normalized reduces the spacing tuple to
min(spacing) and divides a moment of total order q by
min(spacing) ** q.

The current choice is homogeneous in length, so it correctly preserves the
existing documented/tested behavior when all spacing values are multiplied
by the same scalar. The narrower question is whether using the smallest pixel
edge is the intended scalar normalization for anisotropic data.

For a continuous-density interpretation, let d be the number of dimensions,
q = sum(p) the total moment order, and V = prod(spacing) the voxel volume.
The corresponding continuous moments are approximated by

M_p ~= V * mu_p and M_0 ~= V * mu_0.

Substituting these into the usual normalized central moment gives

eta_p ~= mu_p / (mu_0 ** (1 + q / d) * V ** (q / d)).

This uses the geometric-mean spacing V ** (1 / d) as the scalar length
scale. It reduces to the current expression for isotropic spacing, but differs
from min(spacing) for anisotropic grids.

Reproducible example

import numpy as np
from skimage.measure import moments_central, moments_normalized

image = np.ones((200, 400), dtype=float)
spacing = (0.01, 0.02)

mu = moments_central(image, spacing=spacing)
nu = moments_normalized(mu, spacing=spacing)

q = 2
d = image.ndim
voxel_volume = np.prod(spacing)
nu_volume_aware = mu[0, 2] / (
    mu[0, 0] ** (1 + q / d) * voxel_volume ** (q / d)
)

height = image.shape[0] * spacing[0]
width = image.shape[1] * spacing[1]
continuous_rectangle = width / (12 * height)

print(nu[0, 2])             # 0.6666625 with min(spacing)
print(nu_volume_aware)       # 0.33333125
print(continuous_rectangle)  # 0.3333333333333333

Here the sampled rectangle has physical height 2 and width 8. The
volume-aware discrete value approaches the continuous rectangle's normalized
second moment as the grid is refined, whereas the min(spacing) value retains
a factor determined by the spacing anisotropy.

This is not a request for exact equality between differently sampled
rasters. Point-sampled discrete moments have resolution-dependent quadrature
error, so such equality is not generally achievable without finite-pixel
integration corrections.

Question and proposed scope

Is min(spacing) intentionally defining normalized moments relative to the
finest pixel edge? If so, documenting that convention and its implications for
anisotropic data would make the API contract clearer.

If continuous-density semantics are intended instead, would replacing the
scalar factor with prod(spacing) ** (q / d) be appropriate? A focused PR
could preserve the existing common-rescaling test, add 2D/3D checks against
the explicit volume-weighted formula, and document the observable output
change for anisotropic spacing.

The current min(spacing) expression was introduced in #6296. I could not
find discussion of the scalar-spacing choice in that PR's public review.

AI assistance disclosure: Codex was used to inspect the implementation and
history, check the algebra and numerical example, and help draft this report.
The cited source, history, duplicate search, and numerical outputs were checked
against current main before posting.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions