update docs and tests
This commit is contained in:
@@ -33,10 +33,13 @@ class RadialProfile:
|
||||
FUNCTION = "process"
|
||||
|
||||
DESCRIPTION = (
|
||||
"Compute the azimuthally averaged radial profile from a centre point. "
|
||||
"Compute an azimuthally averaged profile around a centre point. "
|
||||
"At each radius, every pixel in the full 360° ring is averaged together, "
|
||||
"so the profile is direction-independent — there is no clockwise/counter-clockwise "
|
||||
"traversal and no start/end point along the ring. "
|
||||
"Drag the centre marker on the preview to reposition the profile, "
|
||||
"drag either end marker to change the radius. "
|
||||
"Output x-axis is radius in physical xy units. "
|
||||
"or drag either end marker (both just set the outer radius) to change the extent. "
|
||||
"Output x-axis is radius in physical xy units."
|
||||
)
|
||||
|
||||
KEYWORDS = ("azimuthal average", "ring average", "circular", "isotropic")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Radial Profile
|
||||
|
||||
Compute the azimuthally averaged radial profile from a centre point. The output x-axis is radius in physical xy units. Equivalent to gwy_data_field_angular_average used by Gwyddion's Radial Profile tool.
|
||||
Compute an **azimuthally averaged** profile around a centre point on a DATA_FIELD. At each radius, every pixel in the full 360° ring around the centre is averaged together, so the profile is direction-independent — there is no clockwise/counter-clockwise traversal and no start or end point along the ring. The output is a single 1-D profile: value vs. radius.
|
||||
|
||||
## Inputs
|
||||
|
||||
@@ -18,11 +18,13 @@ Compute the azimuthally averaged radial profile from a centre point. The output
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| cx | FLOAT | 0.5 | Centre x position as a fraction of field width (0 = left, 1 = right) |
|
||||
| cy | FLOAT | 0.5 | Centre y position as a fraction of field height (0 = top, 1 = bottom) |
|
||||
| n_bins | INT | 128 | Number of radial bins (4-4096) |
|
||||
|
||||
## Interactive preview
|
||||
|
||||
The dashed circle around the centre shows the outer radius used by the profile. Pixels beyond it are not included in the averaging.
|
||||
|
||||
## Notes
|
||||
|
||||
- Pixels are assigned to radial bins by Euclidean distance; bins near the centre contain fewer pixels and may be noisier.
|
||||
- Pixels are assigned to radial bins by Euclidean distance from the centre; inner bins contain fewer pixels and may be noisier.
|
||||
- Physical x-axis units come from the field's si_unit_xy; uncalibrated fields produce pixel-unit radii.
|
||||
|
||||
@@ -11,7 +11,7 @@ def test_radial_profile_constant_field():
|
||||
node = RadialProfile()
|
||||
field = make_field(data=np.full((64, 64), 2.5))
|
||||
|
||||
result, = node.process(field, cx=0.5, cy=0.5, n_bins=32)
|
||||
result, = node.process(field, n_bins=32, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
assert isinstance(result, LineData)
|
||||
assert len(result.data) == 32
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_radial_profile_units():
|
||||
node = RadialProfile()
|
||||
field = make_field()
|
||||
|
||||
result, = node.process(field, cx=0.5, cy=0.5, n_bins=32)
|
||||
result, = node.process(field, n_bins=32, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
assert result.x_unit == field.si_unit_xy
|
||||
assert result.y_unit == field.si_unit_z
|
||||
|
||||
@@ -38,7 +38,7 @@ def test_radial_profile_x_axis_monotone():
|
||||
node = RadialProfile()
|
||||
field = make_field()
|
||||
|
||||
result, = node.process(field, cx=0.5, cy=0.5, n_bins=64)
|
||||
result, = node.process(field, n_bins=64, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
assert result.x_axis[0] >= 0.0
|
||||
assert np.all(np.diff(result.x_axis) > 0)
|
||||
|
||||
@@ -50,7 +50,7 @@ def test_radial_profile_off_centre():
|
||||
node = RadialProfile()
|
||||
field = make_field(data=np.ones((64, 64)))
|
||||
|
||||
result, = node.process(field, cx=0.0, cy=0.0, n_bins=32)
|
||||
result, = node.process(field, n_bins=32, cx=0.0, cy=0.0, ex=1.0, ey=1.0)
|
||||
assert len(result.data) == 32
|
||||
finite = result.data[np.isfinite(result.data)]
|
||||
assert np.allclose(finite, 1.0, atol=1e-10)
|
||||
@@ -67,7 +67,7 @@ def test_radial_profile_radial_symmetry():
|
||||
data = np.cos(r * np.pi / (xres / 2.0))
|
||||
field = make_field(data=data)
|
||||
|
||||
result, = node.process(field, cx=0.5, cy=0.5, n_bins=32)
|
||||
result, = node.process(field, n_bins=32, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
finite = result.data[np.isfinite(result.data)]
|
||||
# The profile should vary (not constant)
|
||||
assert np.std(finite) > 0.01
|
||||
@@ -80,6 +80,25 @@ def test_radial_profile_n_bins():
|
||||
field = make_field()
|
||||
|
||||
for n in (16, 64, 256):
|
||||
result, = node.process(field, cx=0.5, cy=0.5, n_bins=n)
|
||||
result, = node.process(field, n_bins=n, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
assert len(result.data) == n
|
||||
assert len(result.x_axis) == n
|
||||
|
||||
|
||||
def test_radial_profile_radius_controlled_by_endpoint():
|
||||
"""The outer radius is set by the distance from (cx,cy) to (ex,ey)."""
|
||||
from backend.nodes.radial_profile import RadialProfile
|
||||
|
||||
node = RadialProfile()
|
||||
field = make_field()
|
||||
|
||||
# End at (1.0, 0.5): radius = 0.5 * xreal
|
||||
short, = node.process(field, n_bins=32, cx=0.5, cy=0.5, ex=1.0, ey=0.5)
|
||||
expected_r_short = 0.5 * field.xreal
|
||||
assert np.isclose(short.x_axis[-1], expected_r_short, rtol=0.05)
|
||||
|
||||
# End at corner: radius = sqrt(xreal^2 + yreal^2) * 0.5 (half-diagonal)
|
||||
diag, = node.process(field, n_bins=32, cx=0.5, cy=0.5, ex=1.0, ey=1.0)
|
||||
expected_r_diag = 0.5 * np.hypot(field.xreal, field.yreal)
|
||||
assert np.isclose(diag.x_axis[-1], expected_r_diag, rtol=0.05)
|
||||
assert diag.x_axis[-1] > short.x_axis[-1]
|
||||
|
||||
Reference in New Issue
Block a user