add radial profile
This commit is contained in:
@@ -2,8 +2,14 @@ from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from backend.data_types import (
|
||||
DataField,
|
||||
LineData,
|
||||
encode_preview,
|
||||
render_datafield_preview,
|
||||
)
|
||||
from backend.execution_context import emit_overlay
|
||||
from backend.node_registry import register_node
|
||||
from backend.data_types import DataField, LineData
|
||||
|
||||
|
||||
@register_node(display_name="Radial Profile")
|
||||
@@ -13,9 +19,11 @@ class RadialProfile:
|
||||
return {
|
||||
"required": {
|
||||
"field": ("DATA_FIELD",),
|
||||
"cx": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"cy": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"n_bins": ("INT", {"default": 128, "min": 4, "max": 4096, "step": 1}),
|
||||
"cx": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "hidden": True}),
|
||||
"cy": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "hidden": True}),
|
||||
"ex": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01, "hidden": True}),
|
||||
"ey": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "hidden": True}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +34,34 @@ class RadialProfile:
|
||||
|
||||
DESCRIPTION = (
|
||||
"Compute the azimuthally averaged radial profile from a centre point. "
|
||||
"cx/cy give the centre as a fraction of the field width/height (0.5 = centre). "
|
||||
"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. "
|
||||
)
|
||||
|
||||
KEYWORDS = ("azimuthal average", "ring average", "circular", "isotropic")
|
||||
|
||||
def process(self, field: DataField, cx: float, cy: float, n_bins: int) -> tuple:
|
||||
def process(
|
||||
self,
|
||||
field: DataField,
|
||||
n_bins: int,
|
||||
cx: float,
|
||||
cy: float,
|
||||
ex: float,
|
||||
ey: float,
|
||||
) -> tuple:
|
||||
yres, xres = field.data.shape
|
||||
|
||||
# Centre in physical coordinates (matches Gwyddion: xc = cx*xreal + xoff)
|
||||
cx = float(np.clip(cx, 0.0, 1.0))
|
||||
cy = float(np.clip(cy, 0.0, 1.0))
|
||||
ex = float(np.clip(ex, 0.0, 1.0))
|
||||
ey = float(np.clip(ey, 0.0, 1.0))
|
||||
|
||||
xc_phys = cx * field.xreal + field.xoff
|
||||
yc_phys = cy * field.yreal + field.yoff
|
||||
xe_phys = ex * field.xreal + field.xoff
|
||||
ye_phys = ey * field.yreal + field.yoff
|
||||
|
||||
# Pixel-centre physical coordinates
|
||||
xs = (np.arange(xres) + 0.5) * field.dx + field.xoff
|
||||
ys = (np.arange(yres) + 0.5) * field.dy + field.yoff
|
||||
gx, gy = np.meshgrid(xs, ys)
|
||||
@@ -47,20 +69,19 @@ class RadialProfile:
|
||||
r = np.hypot(gx - xc_phys, gy - yc_phys).ravel()
|
||||
values = field.data.ravel()
|
||||
|
||||
# Maximum radius — farthest pixel from centre
|
||||
r_max = float(r.max())
|
||||
if r_max == 0.0:
|
||||
r_max = float(np.hypot(xe_phys - xc_phys, ye_phys - yc_phys))
|
||||
if r_max <= 0.0:
|
||||
r_max = max(field.dx, field.dy)
|
||||
|
||||
# Bin by radius — matches Gwyddion's lineres-bin approach
|
||||
bin_edges = np.linspace(0.0, r_max, n_bins + 1)
|
||||
mask = r <= r_max
|
||||
idx = np.clip(
|
||||
np.floor(n_bins * r / r_max).astype(np.intp), 0, n_bins - 1
|
||||
np.floor(n_bins * r[mask] / r_max).astype(np.intp), 0, n_bins - 1
|
||||
)
|
||||
|
||||
sums = np.zeros(n_bins)
|
||||
counts = np.zeros(n_bins, dtype=np.intp)
|
||||
np.add.at(sums, idx, values)
|
||||
np.add.at(sums, idx, values[mask])
|
||||
np.add.at(counts, idx, 1)
|
||||
|
||||
with np.errstate(invalid="ignore"):
|
||||
@@ -68,6 +89,16 @@ class RadialProfile:
|
||||
|
||||
centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])
|
||||
|
||||
emit_overlay({
|
||||
"kind": "radial_profile",
|
||||
"section_title": "Radial Profile",
|
||||
"image": encode_preview(render_datafield_preview(field, field.colormap)),
|
||||
"cx": cx,
|
||||
"cy": cy,
|
||||
"ex": ex,
|
||||
"ey": ey,
|
||||
})
|
||||
|
||||
return (LineData(
|
||||
data=profile,
|
||||
x_axis=centers,
|
||||
|
||||
Reference in New Issue
Block a user