61 lines
1.9 KiB
Python
61 lines
1.9 KiB
Python
"""Zero crossing detection — edge detection via LoG zero crossings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
from scipy.ndimage import gaussian_laplace
|
|
|
|
from backend.node_registry import register_node
|
|
from backend.data_types import DataField
|
|
|
|
|
|
@register_node(display_name="Zero Crossing")
|
|
class ZeroCrossing:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"field": ("DATA_FIELD",),
|
|
"sigma": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 20.0, "step": 0.1}),
|
|
"threshold": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
|
}
|
|
}
|
|
|
|
OUTPUTS = (
|
|
('DATA_FIELD', 'edges'),
|
|
)
|
|
FUNCTION = "process"
|
|
|
|
DESCRIPTION = (
|
|
"Detect edges by finding zero crossings of the Laplacian of Gaussian "
|
|
"(LoG). Sigma controls the Gaussian smoothing scale. Threshold filters "
|
|
"out weak edges (relative to the LoG range). "
|
|
)
|
|
|
|
def process(self, field: DataField, sigma: float, threshold: float) -> tuple:
|
|
data = np.asarray(field.data, dtype=np.float64)
|
|
|
|
# Compute LoG
|
|
log = gaussian_laplace(data, sigma=sigma)
|
|
|
|
# Find zero crossings: adjacent pixels with opposite signs
|
|
edges = np.zeros_like(data)
|
|
|
|
# Horizontal crossings
|
|
sign_diff_x = log[:, :-1] * log[:, 1:]
|
|
cross_x = sign_diff_x < 0
|
|
strength_x = np.abs(log[:, :-1] - log[:, 1:])
|
|
|
|
# Vertical crossings
|
|
sign_diff_y = log[:-1, :] * log[1:, :]
|
|
cross_y = sign_diff_y < 0
|
|
strength_y = np.abs(log[:-1, :] - log[1:, :])
|
|
|
|
# Apply threshold
|
|
max_strength = max(strength_x.max(), strength_y.max(), 1e-30)
|
|
edges[:, :-1] += cross_x & (strength_x > threshold * max_strength)
|
|
edges[:-1, :] += cross_y & (strength_y > threshold * max_strength)
|
|
|
|
result = (edges > 0).astype(np.float64)
|
|
return (field.replace(data=result, si_unit_z=""),)
|