synthetic surface and spm specific features
This commit is contained in:
89
backend/nodes/sem_simulation.py
Normal file
89
backend/nodes/sem_simulation.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""SEM simulation — scanning electron microscopy image simulation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
from scipy.ndimage import gaussian_filter, shift
|
||||
|
||||
from backend.node_registry import register_node
|
||||
from backend.data_types import DataField
|
||||
|
||||
|
||||
@register_node(display_name="SEM Simulation")
|
||||
class SEMSimulation:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"field": ("DATA_FIELD",),
|
||||
"method": (["integration", "monte_carlo"],),
|
||||
"sigma": ("FLOAT", {
|
||||
"default": 3.0, "min": 0.1, "max": 50.0, "step": 0.1,
|
||||
}),
|
||||
"n_samples": ("INT", {
|
||||
"default": 100, "min": 10, "max": 10000,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
OUTPUTS = (
|
||||
('DATA_FIELD', 'result'),
|
||||
)
|
||||
FUNCTION = "process"
|
||||
|
||||
DESCRIPTION = (
|
||||
"Simulates scanning electron microscopy imaging from topography data. "
|
||||
"The integration method computes the surface slope (gradient magnitude) "
|
||||
"and applies Gaussian smoothing to approximate the secondary electron "
|
||||
"yield, modelling the beam interaction volume. The Monte Carlo method "
|
||||
"stochastically samples neighbour height differences weighted by a "
|
||||
"Gaussian kernel to estimate the local surface visibility, producing "
|
||||
"edge-enhanced contrast similar to real SEM images."
|
||||
)
|
||||
|
||||
def process(self, field: DataField, method: str, sigma: float,
|
||||
n_samples: int) -> tuple:
|
||||
data = np.asarray(field.data, dtype=np.float64)
|
||||
|
||||
if method == "integration":
|
||||
result = self._integration(data, field.dx, field.dy, sigma)
|
||||
elif method == "monte_carlo":
|
||||
result = self._monte_carlo(data, sigma, n_samples)
|
||||
else:
|
||||
raise ValueError(f"Unknown SEM simulation method: {method!r}")
|
||||
|
||||
return (field.replace(data=result, si_unit_z=""),)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _integration(data: np.ndarray, dx: float, dy: float,
|
||||
sigma: float) -> np.ndarray:
|
||||
"""Gradient-magnitude method with Gaussian interaction smoothing."""
|
||||
dz_dy, dz_dx = np.gradient(data, dy, dx)
|
||||
# SEM signal ~ local slope (edge enhancement)
|
||||
slope = np.sqrt(dz_dx**2 + dz_dy**2)
|
||||
# Gaussian blur to simulate beam interaction volume
|
||||
result = gaussian_filter(slope, sigma=sigma)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _monte_carlo(data: np.ndarray, sigma: float,
|
||||
n_samples: int) -> np.ndarray:
|
||||
"""Stochastic height-difference sampling with Gaussian weighting."""
|
||||
rng = np.random.default_rng(42)
|
||||
result = np.zeros_like(data)
|
||||
|
||||
for _ in range(n_samples):
|
||||
dx_off = rng.normal(0, sigma)
|
||||
dy_off = rng.normal(0, sigma)
|
||||
dist = math.sqrt(dx_off**2 + dy_off**2)
|
||||
if dist > 0:
|
||||
shifted = shift(data, [dy_off, dx_off], mode='reflect')
|
||||
weight = math.exp(-(dx_off**2 + dy_off**2) / (2 * sigma**2))
|
||||
result += weight * (data - shifted) / dist
|
||||
|
||||
result /= n_samples
|
||||
return result
|
||||
Reference in New Issue
Block a user