low pri features
This commit is contained in:
88
backend/nodes/presentation_ops.py
Normal file
88
backend/nodes/presentation_ops.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Presentation operations -- manage presentation overlays on data fields."""
|
||||
|
||||
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="Presentation Ops")
|
||||
class PresentationOps:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"field": ("DATA_FIELD",),
|
||||
"operation": (["logscale", "extract_presentation", "attach", "blend"],),
|
||||
"blend_factor": ("FLOAT", {
|
||||
"default": 0.5,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"show_when_widget_value": {"operation": ["blend"]},
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"overlay": ("DATA_FIELD", {
|
||||
"show_when_widget_value": {"operation": ["attach", "blend"]},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
OUTPUTS = (
|
||||
('DATA_FIELD', 'result'),
|
||||
)
|
||||
FUNCTION = "process"
|
||||
|
||||
DESCRIPTION = (
|
||||
"Manage presentation overlays on data fields. "
|
||||
"logscale applies logarithmic scaling for visualising data with large dynamic range. "
|
||||
"extract_presentation normalises the field to [0, 1]. "
|
||||
"attach replaces the field data with an overlay (resampled if needed). "
|
||||
"blend linearly mixes the field and overlay by a configurable factor. "
|
||||
"Equivalent to Gwyddion's presentationops.c module."
|
||||
)
|
||||
|
||||
def process(self, field: DataField, operation: str, blend_factor: float,
|
||||
overlay: DataField | None = None) -> tuple:
|
||||
data = np.asarray(field.data, dtype=np.float64)
|
||||
|
||||
if operation == "logscale":
|
||||
data_pos = data - data.min() + 1e-30
|
||||
result = np.log10(data_pos)
|
||||
|
||||
elif operation == "extract_presentation":
|
||||
dmin, dmax = data.min(), data.max()
|
||||
if dmax > dmin:
|
||||
result = (data - dmin) / (dmax - dmin)
|
||||
else:
|
||||
result = np.zeros_like(data)
|
||||
|
||||
elif operation == "attach":
|
||||
if overlay is None:
|
||||
raise ValueError("'attach' operation requires an overlay field.")
|
||||
overlay_data = np.asarray(overlay.data, dtype=np.float64)
|
||||
result = self._match_shape(overlay_data, data.shape)
|
||||
|
||||
elif operation == "blend":
|
||||
if overlay is None:
|
||||
raise ValueError("'blend' operation requires an overlay field.")
|
||||
overlay_data = np.asarray(overlay.data, dtype=np.float64)
|
||||
overlay_matched = self._match_shape(overlay_data, data.shape)
|
||||
result = (1.0 - blend_factor) * data + blend_factor * overlay_matched
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown operation: {operation!r}")
|
||||
|
||||
return (field.replace(data=result),)
|
||||
|
||||
@staticmethod
|
||||
def _match_shape(source: np.ndarray, target_shape: tuple[int, ...]) -> np.ndarray:
|
||||
"""Resample *source* to *target_shape* using scipy zoom if shapes differ."""
|
||||
if source.shape == target_shape:
|
||||
return source
|
||||
from scipy.ndimage import zoom
|
||||
factors = tuple(t / s for t, s in zip(target_shape, source.shape))
|
||||
return zoom(source, factors, order=3)
|
||||
Reference in New Issue
Block a user