low pri features
This commit is contained in:
80
backend/nodes/facet_analysis.py
Normal file
80
backend/nodes/facet_analysis.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Facet analysis — orientation distribution of surface facets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from backend.node_registry import register_node
|
||||
from backend.data_types import DataField
|
||||
|
||||
|
||||
@register_node(display_name="Facet Analysis")
|
||||
class FacetAnalysis:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"field": ("DATA_FIELD",),
|
||||
"n_bins": ("INT", {"default": 180, "min": 30, "max": 720}),
|
||||
"kernel_size": ("INT", {"default": 3, "min": 3, "max": 9, "step": 2}),
|
||||
}
|
||||
}
|
||||
|
||||
OUTPUTS = (
|
||||
('DATA_FIELD', 'facet_map'),
|
||||
)
|
||||
FUNCTION = "process"
|
||||
|
||||
DESCRIPTION = (
|
||||
"Compute the facet orientation distribution of a surface. "
|
||||
"Outputs a 2D histogram (stereographic projection) where the x-axis "
|
||||
"is the azimuthal angle (phi) and y-axis is the inclination (theta). "
|
||||
"Intensity represents how much surface area faces each orientation. "
|
||||
"Equivalent to Gwyddion's facet_analysis.c module."
|
||||
)
|
||||
|
||||
def process(self, field: DataField, n_bins: int, kernel_size: int) -> tuple:
|
||||
data = np.asarray(field.data, dtype=np.float64)
|
||||
|
||||
# Compute gradients with Sobel-like kernel for robustness
|
||||
from scipy.ndimage import sobel
|
||||
gx = sobel(data, axis=1) / (kernel_size * field.dx)
|
||||
gy = sobel(data, axis=0) / (kernel_size * field.dy)
|
||||
|
||||
# Convert to spherical angles
|
||||
theta = np.arctan(np.sqrt(gx**2 + gy**2)) # inclination from vertical
|
||||
phi = np.arctan2(gy, gx) # azimuthal angle
|
||||
|
||||
# Map to [0, pi/2] and [0, 2*pi]
|
||||
theta_flat = theta.ravel()
|
||||
phi_flat = (phi.ravel() + 2 * np.pi) % (2 * np.pi)
|
||||
|
||||
# Build 2D histogram (stereographic projection)
|
||||
theta_max = float(theta_flat.max()) if theta_flat.size > 0 else np.pi / 4
|
||||
theta_max = max(theta_max, 0.01)
|
||||
|
||||
n_theta = max(1, n_bins // 4)
|
||||
n_phi = n_bins
|
||||
|
||||
hist, _, _ = np.histogram2d(
|
||||
theta_flat,
|
||||
phi_flat,
|
||||
bins=[n_theta, n_phi],
|
||||
range=[[0.0, theta_max], [0.0, 2 * np.pi]],
|
||||
)
|
||||
|
||||
# Normalise so it represents a probability density
|
||||
total = hist.sum()
|
||||
if total > 0:
|
||||
hist = hist / total
|
||||
|
||||
facet_field = DataField(
|
||||
data=hist,
|
||||
xreal=360.0,
|
||||
yreal=np.degrees(theta_max),
|
||||
si_unit_xy="deg",
|
||||
si_unit_z="",
|
||||
domain="spatial",
|
||||
)
|
||||
|
||||
return (facet_field,)
|
||||
Reference in New Issue
Block a user