fix preview inputs and markup preview
This commit is contained in:
@@ -1088,48 +1088,62 @@ def test_font_node():
|
||||
def test_preview_image():
|
||||
print("=== Test: PreviewImage ===")
|
||||
from backend.nodes.preview_image import PreviewImage
|
||||
from backend.data_types import ImageData
|
||||
from backend.execution_context import active_node, execution_callbacks
|
||||
node = PreviewImage()
|
||||
|
||||
# Set up a capture for the broadcast
|
||||
captured = []
|
||||
PreviewImage._broadcast_fn = lambda node_id, data_uri: captured.append(data_uri)
|
||||
PreviewImage._current_node_id = "test"
|
||||
with execution_callbacks(preview=lambda nid, data_uri: captured.append(data_uri)), active_node("test"):
|
||||
# Preview with a DataField
|
||||
field = make_field()
|
||||
node.preview(colormap="viridis", input=field)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
|
||||
# Preview with a DataField
|
||||
field = make_field()
|
||||
node.preview(colormap="viridis", field=field)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
# Preview with field overlay metadata
|
||||
captured.clear()
|
||||
field_with_overlay = field.replace(overlays=[{"kind": "annotation", "show_scale_bar": True, "show_color_map": False, "text_size": 14.0}])
|
||||
node.preview(colormap="viridis", input=field_with_overlay)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
|
||||
# Preview with field overlay metadata
|
||||
captured.clear()
|
||||
field_with_overlay = field.replace(overlays=[{"kind": "annotation", "show_scale_bar": True, "show_color_map": False, "text_size": 14.0}])
|
||||
node.preview(colormap="viridis", field=field_with_overlay)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
# Preview with a custom colormap input
|
||||
captured.clear()
|
||||
custom_colormap = {
|
||||
"mode": "custom",
|
||||
"stops": [
|
||||
{"position": 0.0, "color": "#000000"},
|
||||
{"position": 0.5, "color": "#ff0000"},
|
||||
{"position": 1.0, "color": "#ffffff"},
|
||||
],
|
||||
}
|
||||
node.preview(colormap="auto", input=field, colormap_map=custom_colormap)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
|
||||
# Preview with a custom colormap input
|
||||
captured.clear()
|
||||
custom_colormap = {
|
||||
"mode": "custom",
|
||||
"stops": [
|
||||
{"position": 0.0, "color": "#000000"},
|
||||
{"position": 0.5, "color": "#ff0000"},
|
||||
{"position": 1.0, "color": "#ffffff"},
|
||||
],
|
||||
}
|
||||
node.preview(colormap="auto", field=field, colormap_map=custom_colormap)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
# Preview with an IMAGE array
|
||||
captured.clear()
|
||||
arr = np.random.default_rng(5).integers(0, 256, (32, 32), dtype=np.uint8)
|
||||
node.preview(colormap="gray", input=arr)
|
||||
assert len(captured) == 1
|
||||
|
||||
# Preview with an IMAGE array
|
||||
captured.clear()
|
||||
arr = np.random.default_rng(5).integers(0, 256, (32, 32), dtype=np.uint8)
|
||||
node.preview(colormap="gray", image=arr)
|
||||
assert len(captured) == 1
|
||||
# Preview with an ANNOTATION_SOURCE carrying a DataField
|
||||
captured.clear()
|
||||
node.preview(colormap="auto", input=field_with_overlay)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
|
||||
# Preview with an ANNOTATION_SOURCE carrying an ImageData
|
||||
captured.clear()
|
||||
annotated_image = ImageData(
|
||||
np.zeros((24, 24, 3), dtype=np.uint8),
|
||||
metadata={"annotation_context": {"xreal": 1e-6, "si_unit_xy": "m"}},
|
||||
)
|
||||
node.preview(colormap="auto", input=annotated_image)
|
||||
assert len(captured) == 1
|
||||
assert captured[0].startswith("data:image/png;base64,")
|
||||
|
||||
# Clean up
|
||||
PreviewImage._broadcast_fn = None
|
||||
print(" PASS\n")
|
||||
|
||||
|
||||
@@ -1138,12 +1152,11 @@ def test_annotations():
|
||||
from backend.nodes.annotations import Annotations
|
||||
from backend.nodes.font_node import Font
|
||||
from backend.data_types import ImageData
|
||||
from backend.execution_context import active_node, execution_callbacks
|
||||
|
||||
node = Annotations()
|
||||
font_node = Font()
|
||||
warnings = []
|
||||
Annotations._broadcast_warning_fn = lambda nid, msg: warnings.append(msg)
|
||||
Annotations._current_node_id = "test"
|
||||
field = DataField(
|
||||
data=np.linspace(0.0, 1.0, 64 * 64, dtype=np.float64).reshape(64, 64),
|
||||
xreal=1e-6,
|
||||
@@ -1157,98 +1170,101 @@ def test_annotations():
|
||||
plain_preview = render_datafield_preview(field, "viridis")
|
||||
assert np.array_equal(plain_preview, base)
|
||||
|
||||
plain_field, = node.render(input=field, colormap="auto", show_scale_bar=False, show_color_map=False)
|
||||
assert isinstance(plain_field, DataField)
|
||||
assert np.array_equal(plain_field.data, field.data)
|
||||
assert plain_field.colormap == "viridis"
|
||||
assert plain_field.overlays[-1]["kind"] == "annotation"
|
||||
plain = render_datafield_preview(plain_field, plain_field.colormap)
|
||||
assert plain.shape == base.shape
|
||||
assert np.array_equal(plain, base)
|
||||
with execution_callbacks(warning=lambda nid, msg: warnings.append(msg)), active_node("test"):
|
||||
plain_field, = node.render(input=field, colormap="auto", show_scale_bar=False, show_color_map=False)
|
||||
assert isinstance(plain_field, DataField)
|
||||
assert np.array_equal(plain_field.data, field.data)
|
||||
assert plain_field.colormap == "viridis"
|
||||
assert plain_field.overlays[-1]["kind"] == "annotation"
|
||||
plain = render_datafield_preview(plain_field, plain_field.colormap)
|
||||
assert plain.shape == base.shape
|
||||
assert np.array_equal(plain, base)
|
||||
|
||||
with_scale_field, = node.render(input=field, colormap="auto", show_scale_bar=True, show_color_map=False)
|
||||
with_scale = render_datafield_preview(with_scale_field, with_scale_field.colormap)
|
||||
assert with_scale.shape == base.shape
|
||||
assert not np.array_equal(with_scale, base)
|
||||
with_scale_field, = node.render(input=field, colormap="auto", show_scale_bar=True, show_color_map=False)
|
||||
with_scale = render_datafield_preview(with_scale_field, with_scale_field.colormap)
|
||||
assert with_scale.shape == base.shape
|
||||
assert not np.array_equal(with_scale, base)
|
||||
|
||||
with_legend_field, = node.render(input=field, colormap="auto", show_scale_bar=False, show_color_map=True)
|
||||
with_legend = render_datafield_preview(with_legend_field, with_legend_field.colormap)
|
||||
assert with_legend.shape[0] == base.shape[0]
|
||||
assert with_legend.shape[1] > base.shape[1]
|
||||
assert with_legend.shape[2] == 3
|
||||
with_legend_field, = node.render(input=field, colormap="auto", show_scale_bar=False, show_color_map=True)
|
||||
with_legend = render_datafield_preview(with_legend_field, with_legend_field.colormap)
|
||||
assert with_legend.shape[0] == base.shape[0]
|
||||
assert with_legend.shape[1] > base.shape[1]
|
||||
assert with_legend.shape[2] == 3
|
||||
|
||||
larger_legend_field, = node.render(
|
||||
input=field,
|
||||
colormap="auto",
|
||||
show_scale_bar=False,
|
||||
show_color_map=True,
|
||||
text_size=28.0,
|
||||
)
|
||||
larger_legend_text = render_datafield_preview(larger_legend_field, larger_legend_field.colormap)
|
||||
assert larger_legend_text.shape == with_legend.shape
|
||||
assert not np.array_equal(larger_legend_text, with_legend)
|
||||
larger_legend_field, = node.render(
|
||||
input=field,
|
||||
colormap="auto",
|
||||
show_scale_bar=False,
|
||||
show_color_map=True,
|
||||
text_size=28.0,
|
||||
)
|
||||
larger_legend_text = render_datafield_preview(larger_legend_field, larger_legend_field.colormap)
|
||||
assert larger_legend_text.shape[0] == with_legend.shape[0]
|
||||
assert larger_legend_text.shape[1] > with_legend.shape[1]
|
||||
assert larger_legend_text.shape[2] == with_legend.shape[2]
|
||||
assert not np.array_equal(larger_legend_text, with_legend)
|
||||
|
||||
annotation_font, = font_node.build("Arial")
|
||||
with_font_field, = node.render(
|
||||
input=field,
|
||||
colormap="auto",
|
||||
show_scale_bar=False,
|
||||
show_color_map=True,
|
||||
text_size=28.0,
|
||||
font=annotation_font,
|
||||
)
|
||||
assert with_font_field.overlays[-1]["font"] == {"family": "Arial", "path": ""}
|
||||
with_font = render_datafield_preview(with_font_field, with_font_field.colormap)
|
||||
assert with_font.shape == with_legend.shape
|
||||
annotation_font, = font_node.build("Arial")
|
||||
with_font_field, = node.render(
|
||||
input=field,
|
||||
colormap="auto",
|
||||
show_scale_bar=False,
|
||||
show_color_map=True,
|
||||
text_size=28.0,
|
||||
font=annotation_font,
|
||||
)
|
||||
assert with_font_field.overlays[-1]["font"] == {"family": "Arial", "path": ""}
|
||||
with_font = render_datafield_preview(with_font_field, with_font_field.colormap)
|
||||
assert with_font.shape[0] == with_legend.shape[0]
|
||||
assert with_font.shape[1] > with_legend.shape[1]
|
||||
assert with_font.shape[2] == with_legend.shape[2]
|
||||
|
||||
with_both_field, = node.render(input=field, colormap="auto", show_scale_bar=True, show_color_map=True)
|
||||
with_both = render_datafield_preview(with_both_field, with_both_field.colormap)
|
||||
assert with_both.shape == with_legend.shape
|
||||
assert not np.array_equal(with_both[:, :base.shape[1]], base)
|
||||
with_both_field, = node.render(input=field, colormap="auto", show_scale_bar=True, show_color_map=True)
|
||||
with_both = render_datafield_preview(with_both_field, with_both_field.colormap)
|
||||
assert with_both.shape == with_legend.shape
|
||||
assert not np.array_equal(with_both[:, :base.shape[1]], base)
|
||||
|
||||
viewport_image = ImageData(
|
||||
np.zeros((48, 64, 3), dtype=np.uint8),
|
||||
metadata={
|
||||
"annotation_context": {
|
||||
"xreal": 2e-6,
|
||||
"si_unit_xy": "m",
|
||||
"legend_min": -1.5,
|
||||
"legend_mid": 0.0,
|
||||
"legend_max": 1.5,
|
||||
"legend_unit": "V",
|
||||
"colormap": "viridis",
|
||||
viewport_image = ImageData(
|
||||
np.zeros((48, 64, 3), dtype=np.uint8),
|
||||
metadata={
|
||||
"annotation_context": {
|
||||
"xreal": 2e-6,
|
||||
"si_unit_xy": "m",
|
||||
"legend_min": -1.5,
|
||||
"legend_mid": 0.0,
|
||||
"legend_max": 1.5,
|
||||
"legend_unit": "V",
|
||||
"colormap": "viridis",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
annotated_image, = node.render(
|
||||
input=viewport_image,
|
||||
colormap="auto",
|
||||
show_scale_bar=True,
|
||||
show_color_map=True,
|
||||
text_size=18.0,
|
||||
)
|
||||
assert isinstance(annotated_image, ImageData)
|
||||
assert annotated_image.shape[0] == viewport_image.shape[0]
|
||||
assert annotated_image.shape[1] > viewport_image.shape[1]
|
||||
assert annotated_image.metadata["annotation_context"]["legend_unit"] == "V"
|
||||
assert not np.array_equal(np.asarray(annotated_image)[:, :viewport_image.shape[1]], np.asarray(viewport_image))
|
||||
assert warnings == []
|
||||
)
|
||||
annotated_image, = node.render(
|
||||
input=viewport_image,
|
||||
colormap="auto",
|
||||
show_scale_bar=True,
|
||||
show_color_map=True,
|
||||
text_size=18.0,
|
||||
)
|
||||
assert isinstance(annotated_image, ImageData)
|
||||
assert annotated_image.shape[0] == viewport_image.shape[0]
|
||||
assert annotated_image.shape[1] > viewport_image.shape[1]
|
||||
assert annotated_image.metadata["annotation_context"]["legend_unit"] == "V"
|
||||
assert not np.array_equal(np.asarray(annotated_image)[:, :viewport_image.shape[1]], np.asarray(viewport_image))
|
||||
assert warnings == []
|
||||
|
||||
plain_image = ImageData(np.zeros((32, 40, 3), dtype=np.uint8))
|
||||
passthrough_image, = node.render(
|
||||
input=plain_image,
|
||||
colormap="auto",
|
||||
show_scale_bar=True,
|
||||
show_color_map=True,
|
||||
text_size=18.0,
|
||||
)
|
||||
assert isinstance(passthrough_image, ImageData)
|
||||
assert passthrough_image.shape == plain_image.shape
|
||||
assert np.array_equal(np.asarray(passthrough_image), np.asarray(plain_image))
|
||||
assert len(warnings) == 1
|
||||
assert "no scale metadata" in warnings[0]
|
||||
|
||||
Annotations._broadcast_warning_fn = None
|
||||
plain_image = ImageData(np.zeros((32, 40, 3), dtype=np.uint8))
|
||||
passthrough_image, = node.render(
|
||||
input=plain_image,
|
||||
colormap="auto",
|
||||
show_scale_bar=True,
|
||||
show_color_map=True,
|
||||
text_size=18.0,
|
||||
)
|
||||
assert isinstance(passthrough_image, ImageData)
|
||||
assert passthrough_image.shape == plain_image.shape
|
||||
assert np.array_equal(np.asarray(passthrough_image), np.asarray(plain_image))
|
||||
assert len(warnings) == 1
|
||||
assert "no scale metadata" in warnings[0]
|
||||
|
||||
print(" PASS\n")
|
||||
|
||||
@@ -1257,67 +1273,74 @@ def test_markup():
|
||||
print("=== Test: Markup ===")
|
||||
from backend.nodes.markup import Markup
|
||||
from backend.data_types import ImageData, _preview_markup_stroke_width
|
||||
from backend.execution_context import active_node, execution_callbacks
|
||||
|
||||
node = Markup()
|
||||
field = make_field(data=np.linspace(0.0, 1.0, 48 * 48, dtype=np.float64).reshape(48, 48))
|
||||
base = render_datafield_preview(field, field.colormap)
|
||||
required_inputs = Markup.INPUT_TYPES()["required"]
|
||||
|
||||
assert _preview_markup_stroke_width(5, 128, 128) == 5
|
||||
assert _preview_markup_stroke_width(5, 2048, 2048) > 5
|
||||
assert required_inputs["shape"][1]["default"] == "arrow"
|
||||
assert required_inputs["stroke_color"][1]["default"] == "#ff0000"
|
||||
|
||||
overlays = []
|
||||
Markup._broadcast_overlay_fn = lambda nid, data: overlays.append(data)
|
||||
Markup._current_node_id = "test"
|
||||
with execution_callbacks(overlay=lambda nid, data: overlays.append(data)), active_node("test"):
|
||||
plain_field, = node.process(
|
||||
input=field,
|
||||
shape="line",
|
||||
stroke_color="#ffd54f",
|
||||
stroke_width=3,
|
||||
markup_shapes="[]",
|
||||
)
|
||||
assert isinstance(plain_field, DataField)
|
||||
assert plain_field.overlays[-1]["kind"] == "markup"
|
||||
plain = render_datafield_preview(plain_field, plain_field.colormap)
|
||||
assert np.array_equal(plain, base)
|
||||
assert overlays[-1]["kind"] == "markup"
|
||||
assert overlays[-1]["shape"] == "line"
|
||||
assert overlays[-1]["stroke_color"] == "#ffd54f"
|
||||
assert overlays[-1]["stroke_width"] == 3
|
||||
assert overlays[-1]["image"].startswith("data:image/png;base64,")
|
||||
|
||||
plain_field, = node.process(
|
||||
input=field,
|
||||
shape="line",
|
||||
stroke_color="#ffd54f",
|
||||
stroke_width=3,
|
||||
markup_shapes="[]",
|
||||
)
|
||||
assert isinstance(plain_field, DataField)
|
||||
assert plain_field.overlays[-1]["kind"] == "markup"
|
||||
plain = render_datafield_preview(plain_field, plain_field.colormap)
|
||||
assert np.array_equal(plain, base)
|
||||
assert overlays[-1]["kind"] == "markup"
|
||||
assert overlays[-1]["image"].startswith("data:image/png;base64,")
|
||||
shapes = json.dumps([
|
||||
{"kind": "line", "x1": 0.1, "y1": 0.1, "x2": 0.9, "y2": 0.9, "width": 3, "color": "#ff0000"},
|
||||
{"kind": "rectangle", "x1": 0.2, "y1": 0.2, "x2": 0.8, "y2": 0.5, "width": 2, "color": "#00ff00"},
|
||||
{"kind": "circle", "x1": 0.25, "y1": 0.55, "x2": 0.55, "y2": 0.85, "width": 2, "color": "#4fc3f7"},
|
||||
{"kind": "arrow", "x1": 0.15, "y1": 0.85, "x2": 0.85, "y2": 0.2, "width": 4, "color": "#ffffff"},
|
||||
])
|
||||
marked_field, = node.process(
|
||||
input=field,
|
||||
shape="arrow",
|
||||
stroke_color="#ffffff",
|
||||
stroke_width=4,
|
||||
markup_shapes=shapes,
|
||||
)
|
||||
marked = render_datafield_preview(marked_field, marked_field.colormap)
|
||||
assert marked.shape == base.shape
|
||||
assert not np.array_equal(marked, base)
|
||||
assert overlays[-1]["shape"] == "arrow"
|
||||
assert overlays[-1]["stroke_color"] == "#ffffff"
|
||||
assert overlays[-1]["stroke_width"] == 4
|
||||
|
||||
shapes = json.dumps([
|
||||
{"kind": "line", "x1": 0.1, "y1": 0.1, "x2": 0.9, "y2": 0.9, "width": 3, "color": "#ff0000"},
|
||||
{"kind": "rectangle", "x1": 0.2, "y1": 0.2, "x2": 0.8, "y2": 0.5, "width": 2, "color": "#00ff00"},
|
||||
{"kind": "circle", "x1": 0.25, "y1": 0.55, "x2": 0.55, "y2": 0.85, "width": 2, "color": "#4fc3f7"},
|
||||
{"kind": "arrow", "x1": 0.15, "y1": 0.85, "x2": 0.85, "y2": 0.2, "width": 4, "color": "#ffffff"},
|
||||
])
|
||||
marked_field, = node.process(
|
||||
input=field,
|
||||
shape="arrow",
|
||||
stroke_color="#ffffff",
|
||||
stroke_width=4,
|
||||
markup_shapes=shapes,
|
||||
)
|
||||
marked = render_datafield_preview(marked_field, marked_field.colormap)
|
||||
assert marked.shape == base.shape
|
||||
assert not np.array_equal(marked, base)
|
||||
viewport_image = ImageData(
|
||||
np.zeros((48, 48, 3), dtype=np.uint8),
|
||||
metadata={"annotation_context": {"xreal": 1e-6, "si_unit_xy": "m"}},
|
||||
)
|
||||
image_markup, = node.process(
|
||||
input=viewport_image,
|
||||
shape="line",
|
||||
stroke_color="#ff0000",
|
||||
stroke_width=4,
|
||||
markup_shapes=json.dumps([
|
||||
{"kind": "line", "x1": 0.1, "y1": 0.2, "x2": 0.9, "y2": 0.8, "width": 4, "color": "#ff0000"},
|
||||
]),
|
||||
)
|
||||
assert isinstance(image_markup, ImageData)
|
||||
assert image_markup.metadata["annotation_context"]["si_unit_xy"] == "m"
|
||||
assert not np.array_equal(np.asarray(image_markup), np.asarray(viewport_image))
|
||||
|
||||
viewport_image = ImageData(
|
||||
np.zeros((48, 48, 3), dtype=np.uint8),
|
||||
metadata={"annotation_context": {"xreal": 1e-6, "si_unit_xy": "m"}},
|
||||
)
|
||||
image_markup, = node.process(
|
||||
input=viewport_image,
|
||||
shape="line",
|
||||
stroke_color="#ff0000",
|
||||
stroke_width=4,
|
||||
markup_shapes=json.dumps([
|
||||
{"kind": "line", "x1": 0.1, "y1": 0.2, "x2": 0.9, "y2": 0.8, "width": 4, "color": "#ff0000"},
|
||||
]),
|
||||
)
|
||||
assert isinstance(image_markup, ImageData)
|
||||
assert image_markup.metadata["annotation_context"]["si_unit_xy"] == "m"
|
||||
assert not np.array_equal(np.asarray(image_markup), np.asarray(viewport_image))
|
||||
|
||||
Markup._broadcast_overlay_fn = None
|
||||
print(" PASS\n")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user