from __future__ import annotations import numpy as np from scipy.ndimage import grey_erosion from backend.node_registry import register_node from backend.data_types import DataField @register_node(display_name="Tip Deconvolution") class TipDeconvolution: @classmethod def INPUT_TYPES(cls): return { "required": { "field": ("DATA_FIELD",), "tip": ("DATA_FIELD",), } } OUTPUTS = ( ('DATA_FIELD', 'surface'), ) FUNCTION = "process" DESCRIPTION = ( "Reconstruct the true surface from a tip-broadened measured image. " "Uses morphological grey erosion (Villarrubia algorithm): " " mytip = flip(tip) − max(flip(tip)) [max shifted to 0] " " surface[y,x] = min_{dy,dx}[image[y+dy, x+dx] − mytip[dy,dx]] " "Connect the tip output from a TipModel node. " "The tip pixel size must match the image pixel size. " "Equivalent to gwy_tip_erosion (tip.c)." ) def process(self, field: DataField, tip: DataField) -> tuple: # Gwyddion gwy_tip_erosion: # mytip = flip(tip) − max(flip(tip)) (values ≤ 0, apex = 0) # result[y,x] = min_{ty,tx}[surface[y+ty, x+tx] − mytip[ty,tx]] tip_flipped = np.flipud(np.fliplr(tip.data)) mytip = tip_flipped - tip_flipped.max() # shift so max = 0 result = grey_erosion(field.data, structure=mytip) return (field.replace(data=result),)