fix preview inputs and markup preview

This commit is contained in:
2026-03-27 21:34:51 -07:00
parent 66f1bca046
commit 63bdc70456
13 changed files with 501 additions and 316 deletions

View File

@@ -711,14 +711,6 @@ def _apply_annotation_overlay_from_context(
if current.ndim == 2:
current = np.repeat(current[:, :, np.newaxis], 3, axis=2)
height, current_width = current.shape[:2]
legend_width = max(72, int(round(current_width * 0.18))) if show_color_map else 0
canvas_width = current_width + legend_width
canvas = np.full((height, canvas_width, 3), 255, dtype=np.uint8)
canvas[:, :current_width] = current
pil_image = Image.fromarray(canvas)
draw = ImageDraw.Draw(pil_image)
base_font_px = max(6, int(round(text_size)))
xreal_raw = context.get("xreal")
@@ -739,6 +731,35 @@ def _apply_annotation_overlay_from_context(
and np.isfinite(legend_max)
and bool(legend_unit)
)
height, current_width = current.shape[:2]
legend_pad_x = max(8, int(round(base_font_px * 0.45)))
legend_gap_x = max(8, int(round(base_font_px * 0.35)))
legend_gradient_width = max(12, int(round(max(current_width * 0.05, base_font_px * 0.75))))
legend_label_images: list[Image.Image] = []
if show_color_map and has_color_legend:
legend_label_images = [
_render_overlay_text(
_format_with_unit(value, legend_unit),
base_font_px,
(20, 20, 20),
font_spec=font_spec,
)
for value in (legend_max, legend_mid, legend_min)
]
max_legend_label_width = max((label.size[0] for label in legend_label_images), default=0)
default_legend_width = max(72, int(round(current_width * 0.18))) if show_color_map else 0
legend_width = max(
default_legend_width,
legend_pad_x * 2 + legend_gradient_width + legend_gap_x + max_legend_label_width,
) if show_color_map else 0
canvas_width = current_width + legend_width
canvas = np.full((height, canvas_width, 3), 255, dtype=np.uint8)
canvas[:, :current_width] = current
pil_image = Image.fromarray(canvas)
draw = ImageDraw.Draw(pil_image)
if show_scale_bar and has_scale_bar and current_width > 0:
target_real = xreal / 5.0
@@ -757,21 +778,24 @@ def _apply_annotation_overlay_from_context(
text = _format_with_unit(bar_real, si_unit_xy)
text_image = _render_overlay_text(text, base_font_px, (255, 255, 255), font_spec=font_spec)
text_w, text_h = text_image.size
label_pad = 2
bg_left = max(0, x0 - 4)
bg_top = max(0, y0 - text_h - label_pad * 3)
bg_right = min(canvas_width, max(x1 + 4, x0 + text_w + 8))
bg_bottom = min(height, y1 + 4)
box_pad_x = max(4, int(round(base_font_px * 0.35)))
box_pad_y = max(3, int(round(base_font_px * 0.22)))
label_gap_y = max(2, int(round(base_font_px * 0.18)))
bar_pad_y = max(4, int(round(base_font_px * 0.25)))
bg_left = max(0, x0 - box_pad_x)
bg_top = max(0, y0 - text_h - label_gap_y - box_pad_y * 2)
bg_right = min(canvas_width, max(x1 + box_pad_x, bg_left + text_w + box_pad_x * 2))
bg_bottom = min(height, y1 + bar_pad_y)
draw.rectangle((bg_left, bg_top, bg_right, bg_bottom), fill=(0, 0, 0))
draw.rectangle((x0, y0, x1, y1), fill=(255, 255, 255))
pil_image.paste(text_image, (x0, bg_top + label_pad), text_image)
pil_image.paste(text_image, (bg_left + box_pad_x, bg_top + box_pad_y), text_image)
if show_color_map and has_color_legend and legend_width > 0:
panel_x0 = current_width
draw.rectangle((panel_x0, 0, canvas_width, height), fill=(245, 245, 245))
grad_x0 = panel_x0 + max(8, legend_width // 7)
grad_w = max(12, legend_width // 5)
grad_y0 = max(10, height // 18)
grad_x0 = panel_x0 + legend_pad_x
grad_w = legend_gradient_width
grad_y0 = max(10, max(height // 18, int(round(base_font_px * 0.5))))
grad_y1 = max(grad_y0 + 10, height - grad_y0)
grad_h = grad_y1 - grad_y0
gradient = np.linspace(1.0, 0.0, grad_h, dtype=np.float64)[:, np.newaxis]
@@ -780,19 +804,9 @@ def _apply_annotation_overlay_from_context(
pil_image.paste(Image.fromarray(gradient_rgb), (grad_x0, grad_y0))
draw.rectangle((grad_x0, grad_y0, grad_x0 + grad_w, grad_y1), outline=(40, 40, 40), width=1)
labels = [
(legend_max, grad_y0),
(legend_mid, grad_y0 + grad_h // 2),
(legend_min, grad_y1),
]
text_x = grad_x0 + grad_w + 8
for value, y_center in labels:
text_image = _render_overlay_text(
_format_with_unit(value, legend_unit),
base_font_px,
(20, 20, 20),
font_spec=font_spec,
)
label_centers = [grad_y0, grad_y0 + grad_h // 2, grad_y1]
text_x = grad_x0 + grad_w + legend_gap_x
for text_image, y_center in zip(legend_label_images, label_centers):
text_y = int(round(y_center - text_image.size[1] / 2))
text_y = max(0, min(height - text_image.size[1], text_y))
pil_image.paste(text_image, (text_x, text_y), text_image)

View File

@@ -22,8 +22,8 @@ class Markup:
return {
"required": {
"input": ("ANNOTATION_SOURCE", {"label": "Input"}),
"shape": (["line", "rectangle", "circle", "arrow"], {"default": "line"}),
"stroke_color": ("STRING", {"default": "#ffd54f", "color_picker": True}),
"shape": (["line", "rectangle", "circle", "arrow"], {"default": "arrow"}),
"stroke_color": ("STRING", {"default": "#ff0000", "color_picker": True}),
"stroke_width": ("INT", {"default": 3, "min": 1, "max": 64, "step": 1}),
"clear_shapes": ("BUTTON", {"label": "Clear Shapes", "set_widgets": {"markup_shapes": "[]"}}),
"markup_shapes": ("STRING", {"default": "[]", "hidden": True}),

View File

@@ -4,6 +4,7 @@ from backend.node_registry import register_node
from backend.execution_context import emit_preview
from backend.data_types import (
COLORMAPS,
DataField,
colormap_to_uint8,
encode_preview,
image_to_uint8,
@@ -21,9 +22,8 @@ class PreviewImage:
"colormap": (["auto"] + list(COLORMAPS), {"hide_when_input_connected": "colormap_map"}),
},
"optional": {
"input": ("ANNOTATION_SOURCE", {"label": "Input"}),
"colormap_map": ("COLORMAP", {"label": "colormap"}),
"image": ("IMAGE",),
"field": ("DATA_FIELD",),
}
}
@@ -31,7 +31,7 @@ class PreviewImage:
FUNCTION = "preview"
OUTPUT_NODE = True
DESCRIPTION = "Display an IMAGE or DATA_FIELD as a coloured thumbnail. Connect either input."
DESCRIPTION = "Display an IMAGE or DATA_FIELD as a coloured thumbnail."
_broadcast_fn = None
_current_node_id: str = ""
@@ -39,10 +39,12 @@ class PreviewImage:
def preview(
self,
colormap: str,
image: np.ndarray | None = None,
field=None,
input=None,
colormap_map=None,
) -> tuple:
field = input if isinstance(input, DataField) else None
image = None if field is not None else input
resolved_colormap = resolve_colormap_input(
colormap,
colormap_input=colormap_map,
@@ -65,7 +67,7 @@ class PreviewImage:
normalized = np.zeros_like(image, dtype=np.float64)
arr_u8 = colormap_to_uint8(normalized, resolved_colormap)
else:
raise ValueError("Connect either an IMAGE or DATA_FIELD input to Preview.")
raise ValueError("Connect an IMAGE or DATA_FIELD input to Preview.")
data_uri = encode_preview(arr_u8)