adding more nodes
This commit is contained in:
89
backend/nodes/immerse_detail.py
Normal file
89
backend/nodes/immerse_detail.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Immerse detail — overlay high-resolution detail onto lower-resolution overview."""
|
||||
|
||||
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="Immerse Detail")
|
||||
class ImmerseDetail:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"overview": ("DATA_FIELD",),
|
||||
"detail": ("DATA_FIELD",),
|
||||
"blend": (["replace", "average"], {"default": "replace"}),
|
||||
}
|
||||
}
|
||||
|
||||
OUTPUTS = (
|
||||
('DATA_FIELD', 'combined'),
|
||||
)
|
||||
FUNCTION = "process"
|
||||
|
||||
DESCRIPTION = (
|
||||
"Overlay a high-resolution detail scan onto a lower-resolution overview "
|
||||
"image using cross-correlation to find the best position. "
|
||||
)
|
||||
|
||||
def process(self, overview: DataField, detail: DataField, blend: str) -> tuple:
|
||||
ov = np.asarray(overview.data, dtype=np.float64)
|
||||
dt = np.asarray(detail.data, dtype=np.float64)
|
||||
|
||||
# Resample detail to overview pixel size if needed
|
||||
scale_x = detail.dx / overview.dx
|
||||
scale_y = detail.dy / overview.dy
|
||||
|
||||
if abs(scale_x - 1.0) > 0.01 or abs(scale_y - 1.0) > 0.01:
|
||||
from scipy.ndimage import zoom
|
||||
dt = zoom(dt, (scale_y, scale_x), order=1)
|
||||
|
||||
dy_res, dx_res = dt.shape
|
||||
oy_res, ox_res = ov.shape
|
||||
|
||||
if dy_res >= oy_res or dx_res >= ox_res:
|
||||
# Detail is larger than overview, just return overview
|
||||
return (overview,)
|
||||
|
||||
# Cross-correlate to find best position
|
||||
# Use a sliding window approach for small detail
|
||||
best_score = -np.inf
|
||||
best_y, best_x = 0, 0
|
||||
|
||||
dt_norm = dt - dt.mean()
|
||||
dt_std = dt.std()
|
||||
if dt_std < 1e-30:
|
||||
dt_std = 1.0
|
||||
|
||||
# Coarse search with stride
|
||||
stride = max(1, min(dy_res, dx_res) // 4)
|
||||
for iy in range(0, oy_res - dy_res + 1, stride):
|
||||
for ix in range(0, ox_res - dx_res + 1, stride):
|
||||
patch = ov[iy:iy + dy_res, ix:ix + dx_res]
|
||||
score = np.sum((patch - patch.mean()) * dt_norm) / (patch.std() * dt_std + 1e-30)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_y, best_x = iy, ix
|
||||
|
||||
# Fine search around best position
|
||||
for iy in range(max(0, best_y - stride), min(oy_res - dy_res + 1, best_y + stride + 1)):
|
||||
for ix in range(max(0, best_x - stride), min(ox_res - dx_res + 1, best_x + stride + 1)):
|
||||
patch = ov[iy:iy + dy_res, ix:ix + dx_res]
|
||||
score = np.sum((patch - patch.mean()) * dt_norm) / (patch.std() * dt_std + 1e-30)
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_y, best_x = iy, ix
|
||||
|
||||
# Place detail into overview
|
||||
result = ov.copy()
|
||||
if blend == "replace":
|
||||
result[best_y:best_y + dy_res, best_x:best_x + dx_res] = dt
|
||||
else: # average
|
||||
result[best_y:best_y + dy_res, best_x:best_x + dx_res] = \
|
||||
0.5 * (ov[best_y:best_y + dy_res, best_x:best_x + dx_res] + dt)
|
||||
|
||||
return (overview.replace(data=result),)
|
||||
Reference in New Issue
Block a user