rework output type socket to be more flexible

This commit is contained in:
2026-03-28 14:10:17 -07:00
parent 1b831cda5d
commit 3b838deb4d
55 changed files with 213 additions and 117 deletions

View File

@@ -31,7 +31,7 @@ from threading import RLock
from time import perf_counter
from typing import Any, Callable
from backend.node_registry import NODE_CLASS_MAPPINGS
from backend.node_registry import NODE_CLASS_MAPPINGS, get_node_output_types
from backend.execution_context import active_node, execution_callbacks
@@ -455,7 +455,7 @@ class ExecutionEngine:
on_preview(node_id, preview)
return
return_types = getattr(cls, "RETURN_TYPES", ())
return_types = get_node_output_types(cls)
for slot, type_name in enumerate(return_types):
if slot >= len(result):
@@ -536,7 +536,7 @@ class ExecutionEngine:
import numpy as np
from backend.data_types import LineData
return_types = getattr(cls, "RETURN_TYPES", ())
return_types = get_node_output_types(cls)
# Find the y-values (current slot) and try to find an x-axis
y = result[slot]

View File

@@ -15,6 +15,30 @@ NODE_CLASS_MAPPINGS: dict[str, type] = {}
NODE_DISPLAY_NAME_MAPPINGS: dict[str, str] = {}
def get_node_output_specs(cls: type) -> tuple[tuple[str, str], ...]:
raw_outputs = getattr(cls, "OUTPUTS", None)
if raw_outputs is None:
raise AttributeError(f"{cls.__name__} must define OUTPUTS.")
specs: list[tuple[str, str]] = []
for index, output in enumerate(raw_outputs):
if not isinstance(output, (list, tuple)) or len(output) != 2:
raise TypeError(
f"{cls.__name__}.OUTPUTS[{index}] must be a 2-item tuple of (type, name)."
)
type_name, name = output
specs.append((str(type_name), str(name)))
return tuple(specs)
def get_node_output_types(cls: type) -> tuple[str, ...]:
return tuple(type_name for type_name, _ in get_node_output_specs(cls))
def get_node_output_names(cls: type) -> tuple[str, ...]:
return tuple(name for _, name in get_node_output_specs(cls))
def register_node(display_name: str | None = None):
"""
Class decorator that registers a node class into NODE_CLASS_MAPPINGS.
@@ -25,6 +49,7 @@ def register_node(display_name: str | None = None):
...
"""
def decorator(cls: type) -> type:
get_node_output_specs(cls)
name = cls.__name__
NODE_CLASS_MAPPINGS[name] = cls
NODE_DISPLAY_NAME_MAPPINGS[name] = display_name or name
@@ -50,8 +75,8 @@ def get_node_info(class_name: str) -> dict[str, Any]:
"menu_categories": list(menu_metadata.get("menu_categories", [])),
"input": input_types,
"input_order": {k: list(v.keys()) for k, v in input_types.items()},
"output": list(cls.RETURN_TYPES),
"output_name": list(getattr(cls, "RETURN_NAMES", cls.RETURN_TYPES)),
"output": list(get_node_output_types(cls)),
"output_name": list(get_node_output_names(cls)),
"output_node": bool(getattr(cls, "OUTPUT_NODE", False)),
"manual_trigger": bool(getattr(cls, "MANUAL_TRIGGER", False)),
"description": getattr(cls, "DESCRIPTION", ""),

View File

@@ -16,8 +16,9 @@ class ACF:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("acf",)
OUTPUTS = (
('DATA_FIELD', 'acf'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -112,8 +112,10 @@ class AngleMeasure:
},
}
RETURN_TYPES = ("ANNOTATION_SOURCE", "MEASURE_TABLE")
RETURN_NAMES = ("output", "measurements")
OUTPUTS = (
('ANNOTATION_SOURCE', 'output'),
('MEASURE_TABLE', 'measurements'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -43,8 +43,9 @@ class Annotations:
},
}
RETURN_TYPES = ("ANNOTATION_SOURCE",)
RETURN_NAMES = ("Output",)
OUTPUTS = (
('ANNOTATION_SOURCE', 'output'),
)
FUNCTION = "render"
DESCRIPTION = (

View File

@@ -23,8 +23,9 @@ class ColorMap:
}
}
RETURN_TYPES = ("COLORMAP",)
RETURN_NAMES = ("colormap",)
OUTPUTS = (
('COLORMAP', 'colormap'),
)
FUNCTION = "build"
DESCRIPTION = (

View File

@@ -17,8 +17,9 @@ class ColormapAdjust:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -15,8 +15,9 @@ class Coordinate:
}
}
RETURN_TYPES = ("COORD",)
RETURN_NAMES = ("point",)
OUTPUTS = (
('COORD', 'point'),
)
FUNCTION = "process"
DESCRIPTION = "Output a fractional (x, y) coordinate pair in [0, 1]."

View File

@@ -15,8 +15,9 @@ class CoordinatePair:
}
}
RETURN_TYPES = ("COORDPAIR",)
RETURN_NAMES = ("coord pair",)
OUTPUTS = (
('COORDPAIR', 'coord_pair'),
)
FUNCTION = "process"
DESCRIPTION = "Output a pair of coordinates."

View File

@@ -26,8 +26,9 @@ class CropResizeField:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -27,8 +27,10 @@ class CrossSection:
},
}
RETURN_TYPES = ("LINE", "COORDPAIR",)
RETURN_NAMES = ("profile", "marker pair",)
OUTPUTS = (
('LINE', 'profile'),
('COORDPAIR', 'marker_pair'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -27,8 +27,10 @@ class Cursors:
},
}
RETURN_TYPES = ("MEASURE_TABLE", "COORDPAIR",)
RETURN_NAMES = ("measurement", "coord pair",)
OUTPUTS = (
('MEASURE_TABLE', 'measurement'),
('COORDPAIR', 'coord_pair'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -287,8 +287,12 @@ class Curvature:
},
}
RETURN_TYPES = ("ANNOTATION_SOURCE", "MEASURE_TABLE", "LINE", "LINE")
RETURN_NAMES = ("output", "measurements", "profile 1", "profile 2")
OUTPUTS = (
('ANNOTATION_SOURCE', 'output'),
('MEASURE_TABLE', 'measurements'),
('LINE', 'profile_1'),
('LINE', 'profile_2'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -22,8 +22,9 @@ class DrawMask:
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -16,8 +16,9 @@ class EdgeDetect:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("edges",)
OUTPUTS = (
('DATA_FIELD', 'edges'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -123,8 +123,9 @@ class FacetLevelField:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("leveled",)
OUTPUTS = (
('DATA_FIELD', 'leveled'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -21,8 +21,12 @@ class FFT2D:
}
}
RETURN_TYPES = ("DATA_FIELD", "DATA_FIELD", "DATA_FIELD", "DATA_FIELD")
RETURN_NAMES = ("log_magnitude", "magnitude", "phase", "psdf")
OUTPUTS = (
('DATA_FIELD', 'log_magnitude'),
('DATA_FIELD', 'magnitude'),
('DATA_FIELD', 'phase'),
('DATA_FIELD', 'psdf'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -29,8 +29,9 @@ class FFTFilter1D:
}
}
RETURN_TYPES = ("LINE",)
RETURN_NAMES = ("filtered",)
OUTPUTS = (
('LINE', 'filtered'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -30,8 +30,9 @@ class FFTFilter2D:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("filtered",)
OUTPUTS = (
('DATA_FIELD', 'filtered'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -15,8 +15,9 @@ class FixZero:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("zeroed",)
OUTPUTS = (
('DATA_FIELD', 'zeroed'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -19,8 +19,9 @@ class FlipField:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -13,8 +13,9 @@ class Folder:
}
}
RETURN_TYPES = ("DIRECTORY",)
RETURN_NAMES = ("directory",)
OUTPUTS = (
('DIRECTORY', 'directory'),
)
FUNCTION = "list_files"
DESCRIPTION = (

View File

@@ -19,8 +19,9 @@ class Font:
}
}
RETURN_TYPES = ("FONT",)
RETURN_NAMES = ("font",)
OUTPUTS = (
('FONT', 'font'),
)
FUNCTION = "build"
DESCRIPTION = (

View File

@@ -304,8 +304,11 @@ class FractalDimension:
}
}
RETURN_TYPES = ("FLOAT", "LINE", "MEASURE_TABLE")
RETURN_NAMES = ("dimension", "curve", "measurements")
OUTPUTS = (
('FLOAT', 'dimension'),
('LINE', 'curve'),
('MEASURE_TABLE', 'measurements'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -14,8 +14,9 @@ class GaussianFilter:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("filtered",)
OUTPUTS = (
('DATA_FIELD', 'filtered'),
)
FUNCTION = "process"
DESCRIPTION = "Apply a Gaussian blur. Equivalent to gwy_data_field_filter_gaussian."

View File

@@ -17,8 +17,9 @@ class GrainAnalysis:
}
}
RETURN_TYPES = ("RECORD_TABLE",)
RETURN_NAMES = ("grain_stats",)
OUTPUTS = (
('RECORD_TABLE', 'grain_stats'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -103,8 +103,9 @@ class GrainDistanceTransform:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("distance",)
OUTPUTS = (
('DATA_FIELD', 'distance'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -21,8 +21,10 @@ class Histogram:
}
}
RETURN_TYPES = ("MEASURE_TABLE", "COORDPAIR",)
RETURN_NAMES = ("measurements", "marker pair",)
OUTPUTS = (
('MEASURE_TABLE', 'measurements'),
('COORDPAIR', 'marker_pair'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -24,8 +24,9 @@ class Image:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "load"
DESCRIPTION = (

View File

@@ -19,8 +19,9 @@ class ImageDemo:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "load"
DESCRIPTION = "Load a bundled demo file so you can try the app without providing your own data."

View File

@@ -18,8 +18,9 @@ class InverseFFT2D:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("image",)
OUTPUTS = (
('DATA_FIELD', 'image'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -307,8 +307,11 @@ class LineCorrection:
},
}
RETURN_TYPES = ("DATA_FIELD", "DATA_FIELD", "LINE")
RETURN_NAMES = ("corrected", "background", "row shifts")
OUTPUTS = (
('DATA_FIELD', 'corrected'),
('DATA_FIELD', 'background'),
('LINE', 'row_shifts'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -33,8 +33,9 @@ class Markup:
}
}
RETURN_TYPES = ("ANNOTATION_SOURCE",)
RETURN_NAMES = ("Output",)
OUTPUTS = (
('ANNOTATION_SOURCE', 'output'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -21,8 +21,9 @@ class MaskInvert:
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = "Invert a binary mask — swap masked and unmasked regions."

View File

@@ -28,8 +28,9 @@ class MaskMorphology:
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -37,8 +37,9 @@ class MaskOperations:
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -14,8 +14,9 @@ class MedianFilter:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("filtered",)
OUTPUTS = (
('DATA_FIELD', 'filtered'),
)
FUNCTION = "process"
DESCRIPTION = "Apply a median filter. Equivalent to gwy_data_field_filter_median."

View File

@@ -14,8 +14,9 @@ class Number:
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -61,8 +61,9 @@ class PlaneLevelField:
},
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("leveled",)
OUTPUTS = (
('DATA_FIELD', 'leveled'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -16,8 +16,10 @@ class PolyLevelField:
}
}
RETURN_TYPES = ("DATA_FIELD", "DATA_FIELD")
RETURN_NAMES = ("leveled", "background")
OUTPUTS = (
('DATA_FIELD', 'leveled'),
('DATA_FIELD', 'background'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -30,7 +30,7 @@ class PreviewImage:
}
}
RETURN_TYPES = ()
OUTPUTS = ()
FUNCTION = "preview"
OUTPUT_NODE = True

View File

@@ -15,7 +15,7 @@ class PrintTable:
}
}
RETURN_TYPES = ()
OUTPUTS = ()
FUNCTION = "print_table"
OUTPUT_NODE = True

View File

@@ -17,8 +17,9 @@ class PSDF:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("psdf",)
OUTPUTS = (
('DATA_FIELD', 'psdf'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -23,8 +23,9 @@ class RangeSlider:
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -18,8 +18,9 @@ class RotateField:
}
}
RETURN_TYPES = ("DATA_FIELD",)
RETURN_NAMES = ("field",)
OUTPUTS = (
('DATA_FIELD', 'field'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -61,7 +61,7 @@ class Save:
},
}
RETURN_TYPES = ()
OUTPUTS = ()
FUNCTION = "save"
OUTPUT_NODE = True

View File

@@ -47,7 +47,7 @@ class SaveImage:
"optional": optional,
}
RETURN_TYPES = ()
OUTPUTS = ()
FUNCTION = "save"
OUTPUT_NODE = True

View File

@@ -185,8 +185,10 @@ class ScarRemoval:
}
}
RETURN_TYPES = ("DATA_FIELD", "IMAGE")
RETURN_NAMES = ("corrected", "scar mask")
OUTPUTS = (
('DATA_FIELD', 'corrected'),
('IMAGE', 'scar_mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -14,8 +14,9 @@ class Statistics:
}
}
RETURN_TYPES = ("MEASURE_TABLE",)
RETURN_NAMES = ("stats",)
OUTPUTS = (
('MEASURE_TABLE', 'stats'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -49,8 +49,9 @@ class Stats:
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -21,8 +21,9 @@ class ThresholdMask:
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -24,8 +24,9 @@ class ValueDisplay:
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "display_value"
DESCRIPTION = "Display a FLOAT, or a selected numeric row from a measurement table, and pass the value through unchanged."

View File

@@ -135,8 +135,10 @@ class View3D:
},
}
RETURN_TYPES = ("MESH_MODEL", "IMAGE")
RETURN_NAMES = ("mesh", "viewport")
OUTPUTS = (
('MESH_MODEL', 'mesh'),
('IMAGE', 'viewport'),
)
FUNCTION = "render"
OUTPUT_NODE = True

View File

@@ -221,8 +221,9 @@ class WatershedSegmentation:
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("mask",)
OUTPUTS = (
('IMAGE', 'mask'),
)
FUNCTION = "process"
DESCRIPTION = (

View File

@@ -2425,8 +2425,9 @@ def test_execution_engine_numeric_socket_coercion():
def INPUT_TYPES(cls):
return {"required": {"value": ("INT",)}}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('INT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"
@@ -2439,8 +2440,9 @@ def test_execution_engine_numeric_socket_coercion():
def INPUT_TYPES(cls):
return {"required": {"value": ("FLOAT",)}}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"
@@ -2483,8 +2485,9 @@ def test_execution_engine_caches_unchanged_nodes():
def INPUT_TYPES(cls):
return {"required": {"value": ("FLOAT",)}}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"
@@ -2500,8 +2503,9 @@ def test_execution_engine_caches_unchanged_nodes():
def INPUT_TYPES(cls):
return {"required": {"value": ("FLOAT",)}}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"
@@ -2552,8 +2556,9 @@ def test_execution_engine_only_propagates_real_output_changes():
def INPUT_TYPES(cls):
return {"required": {"value": ("FLOAT",)}}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('INT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"
@@ -2569,8 +2574,9 @@ def test_execution_engine_only_propagates_real_output_changes():
def INPUT_TYPES(cls):
return {"required": {"value": ("INT",)}}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
OUTPUTS = (
('FLOAT', 'value'),
)
FUNCTION = "process"
CATEGORY = "tests"