"""Calibration — apply lateral and height calibration corrections.""" 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="Calibration") class Calibration: @classmethod def INPUT_TYPES(cls): return { "required": { "field": ("DATA_FIELD",), "xy_mode": (["keep", "set_size", "scale"], {"default": "keep"}), "z_mode": (["keep", "set_range", "scale", "offset"], {"default": "keep"}), "xreal_new": ("FLOAT", { "default": 1e-6, "min": 1e-12, "max": 1.0, "step": 1e-9, "show_when_widget_value": {"xy_mode": ["set_size"]}, }), "yreal_new": ("FLOAT", { "default": 1e-6, "min": 1e-12, "max": 1.0, "step": 1e-9, "show_when_widget_value": {"xy_mode": ["set_size"]}, }), "xy_scale": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, "step": 0.001, "show_when_widget_value": {"xy_mode": ["scale"]}, }), "z_min": ("FLOAT", { "default": 0.0, "min": -1e-3, "max": 1e-3, "step": 1e-12, "show_when_widget_value": {"z_mode": ["set_range"]}, }), "z_max": ("FLOAT", { "default": 1e-9, "min": -1e-3, "max": 1e-3, "step": 1e-12, "show_when_widget_value": {"z_mode": ["set_range"]}, }), "z_scale": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 1000.0, "step": 0.001, "show_when_widget_value": {"z_mode": ["scale"]}, }), "z_offset": ("FLOAT", { "default": 0.0, "min": -1e-3, "max": 1e-3, "step": 1e-12, "show_when_widget_value": {"z_mode": ["offset"]}, }), "xy_unit": ("STRING", {"default": ""}), "z_unit": ("STRING", {"default": ""}), } } OUTPUTS = ( ('DATA_FIELD', 'result'), ) FUNCTION = "process" DESCRIPTION = ( "Apply lateral and height calibration corrections to a DATA_FIELD. " "Lateral mode can set explicit physical size or scale uniformly. " "Height mode can rescale to a target range, multiply by a factor, " "or add a constant offset. Optionally override the XY or Z unit strings. " "Equivalent to Gwyddion's calibrate functionality." ) def process( self, field: DataField, xy_mode: str, z_mode: str, xreal_new: float, yreal_new: float, xy_scale: float, z_min: float, z_max: float, z_scale: float, z_offset: float, xy_unit: str, z_unit: str, ) -> tuple: data = np.asarray(field.data, dtype=np.float64).copy() xreal = float(field.xreal) yreal = float(field.yreal) si_unit_xy = field.si_unit_xy si_unit_z = field.si_unit_z # --- lateral calibration --- if xy_mode == "set_size": xreal = float(xreal_new) yreal = float(yreal_new) elif xy_mode == "scale": xreal *= float(xy_scale) yreal *= float(xy_scale) # "keep" → no change # --- height calibration --- if z_mode == "set_range": cur_min = float(data.min()) cur_max = float(data.max()) if cur_max > cur_min: data = float(z_min) + (data - cur_min) * (float(z_max) - float(z_min)) / (cur_max - cur_min) else: data[:] = float(z_min) elif z_mode == "scale": data *= float(z_scale) elif z_mode == "offset": data += float(z_offset) # "keep" → no change # --- unit overrides --- if xy_unit: si_unit_xy = xy_unit if z_unit: si_unit_z = z_unit return (field.replace( data=data, xreal=xreal, yreal=yreal, si_unit_xy=si_unit_xy, si_unit_z=si_unit_z, ),)