From 80b74dfdfd036950319fbfbc400ea4be35d95b56 Mon Sep 17 00:00:00 2001 From: matei jordache Date: Sat, 28 Mar 2026 22:34:10 -0700 Subject: [PATCH] work on curvature node --- backend/nodes/__init__.py | 2 +- backend/nodes/curvature.py | 44 ++++++++++++++----- backend/nodes/histogram.py | 2 +- backend/nodes/value_display.py | 4 +- frontend/src/CustomNode.jsx | 80 +++++++++++++++++++++------------- 5 files changed, 86 insertions(+), 46 deletions(-) diff --git a/backend/nodes/__init__.py b/backend/nodes/__init__.py index 79fcc0e..916120e 100644 --- a/backend/nodes/__init__.py +++ b/backend/nodes/__init__.py @@ -3,7 +3,7 @@ from backend.nodes import ( # IO colormap, crop_resize, - fft_2d_invert, + fft_2d_inverse, filter_fft_1d, filter_fft_2d, filter_gaussian, diff --git a/backend/nodes/curvature.py b/backend/nodes/curvature.py index 643bdfe..a590217 100644 --- a/backend/nodes/curvature.py +++ b/backend/nodes/curvature.py @@ -159,6 +159,8 @@ def _compute_curvature_results( x0 = float(xc / q + 0.5 * xreal + field.xoff) y0 = float(yc / q + 0.5 * yreal + field.yoff) + print(f"debug: {x0}, {y0}, {r1}, {r2}") + return { "degree": float(degree), "x0": x0, @@ -290,8 +292,8 @@ class Curvature: OUTPUTS = ( ('ANNOTATION_SOURCE', 'output'), ('RECORD_TABLE', 'measurements'), - ('LINE', 'profile_1'), - ('LINE', 'profile_2'), + ('LINE', 'profile_x'), + ('LINE', 'profile_y'), ) FUNCTION = "process" @@ -338,7 +340,7 @@ class Curvature: profiles = [] for pair in intersections[:2]: - profiles.append(_profile_from_intersections(field, pair[0], pair[1])) + profiles.append(_profile_from_intersections(field, pair[1], pair[0])) while len(profiles) < 2: profiles.append(_empty_profile(field.si_unit_xy, field.si_unit_z)) @@ -346,18 +348,36 @@ class Curvature: output = field.replace(overlays=[*field.overlays, markup_spec]) table = RecordTable([ - {"quantity": "Center x position", "value": float(results["x0"]), "unit": field.si_unit_xy}, - {"quantity": "Center y position", "value": float(results["y0"]), "unit": field.si_unit_xy}, - {"quantity": "Center value", "value": float(results["z0"]), "unit": field.si_unit_z}, - {"quantity": "Curvature radius 1", "value": float(results["r1"]), "unit": field.si_unit_xy}, - {"quantity": "Curvature radius 2", "value": float(results["r2"]), "unit": field.si_unit_xy}, - {"quantity": "Direction 1", "value": float(np.degrees(results["phi1"])), "unit": "deg"}, - {"quantity": "Direction 2", "value": float(np.degrees(results["phi2"])), "unit": "deg"}, + {"quantity": "Curvature radius 1", "value": results["r1"], "unit": field.si_unit_xy}, + {"quantity": "Curvature radius 2", "value": results["r2"], "unit": field.si_unit_xy}, + {"quantity": "Center x position", "value": results["x0"], "unit": field.si_unit_xy}, + {"quantity": "Center y position", "value": results["y0"], "unit": field.si_unit_xy}, + {"quantity": "Center value", "value": results["z0"], "unit": field.si_unit_z}, + {"quantity": "Direction 1", "value": results["phi1"], "unit": "deg"}, + {"quantity": "Direction 2", "value": results["phi2"], "unit": "deg"}, ]) preview_base = render_datafield_preview(field, field.colormap) - emit_preview(encode_preview(_apply_markup_overlay(preview_base, field, markup_spec))) - emit_table(table) + panels = [] + + for p, title in zip(profiles, ["X Principal Axis", "Y Principal Axis"]): + if len(p.data) > 0: + panels.append({ + "title": title, + "kind": "line_plot", + "line": p.data.tolist(), + "x_axis": p.x_axis.tolist(), + "x_unit": field.si_unit_xy, + }) + panels.append({ + "title": "Overview", + "kind": "image", + "image": encode_preview(_apply_markup_overlay(preview_base, field, markup_spec)), + }) + + emit_preview({"kind": "panels", "panels": panels}) + # emit_table(table) + if warnings: emit_warning(warnings[0]) diff --git a/backend/nodes/histogram.py b/backend/nodes/histogram.py index 1e7baf6..897472b 100644 --- a/backend/nodes/histogram.py +++ b/backend/nodes/histogram.py @@ -90,8 +90,8 @@ class Histogram: }) table = RecordTable([ - {"quantity": "delta Y", "value": yb - ya, "unit": count_unit}, {"quantity": "delta X", "value": xb - xa, "unit": field.si_unit_z}, + {"quantity": "delta Y", "value": yb - ya, "unit": count_unit}, {"quantity": "A position", "value": xa, "unit": field.si_unit_z}, {"quantity": "A count", "value": ya, "unit": count_unit}, {"quantity": "B position", "value": xb, "unit": field.si_unit_z}, diff --git a/backend/nodes/value_display.py b/backend/nodes/value_display.py index 2eecaa7..d5dca76 100644 --- a/backend/nodes/value_display.py +++ b/backend/nodes/value_display.py @@ -1,6 +1,6 @@ from __future__ import annotations from backend.node_registry import register_node -from backend.execution_context import emit_value +from backend.execution_context import emit_table, emit_value from backend.data_types import RecordTable from backend.nodes.helpers import _measurement_entry, _measurement_value, _scalar_payload @@ -13,6 +13,7 @@ class ValueDisplay: "required": { "value": ("FLOAT", { "accepted_types": ["RECORD_TABLE"], + "socket_only": True, }), "measurement": ("STRING", { "default": "", @@ -37,6 +38,7 @@ class ValueDisplay: def display_value(self, value, measurement: str = "") -> tuple: unit = "" if isinstance(value, RecordTable): + emit_table(value) row = _measurement_entry(value, measurement) numeric = _measurement_value(value, measurement) unit = row.get("unit", "") if isinstance(row.get("unit"), str) else "" diff --git a/frontend/src/CustomNode.jsx b/frontend/src/CustomNode.jsx index 3ac0880..b001cda 100644 --- a/frontend/src/CustomNode.jsx +++ b/frontend/src/CustomNode.jsx @@ -1056,6 +1056,9 @@ function CustomNode({ id, data }) { const combinedInputNames = new Set(combinedInputNameByWidgetName.values()); const renderedDataInputs = dataInputs.filter((input) => !combinedInputNames.has(input.name)); + // Computed directly from React props so it updates reliably when tableRows changes. + const nodeTableMeasurementChoices = getMeasurementChoices(data.tableRows || []); + const inlineWidgetsByInput = new Map(); const topWidgets = []; const standaloneWidgets = []; @@ -1153,6 +1156,7 @@ function CustomNode({ id, data }) { widgetValues={data.widgetValues} onChange={ctx.onWidgetChange} openFileBrowser={ctx.openFileBrowser} + measurementChoices={nodeTableMeasurementChoices} /> )} @@ -1219,7 +1223,6 @@ function CustomNode({ id, data }) { {scalarDisplay && (
-
Value
{scalarDisplay.valueText} {scalarDisplay.unitText && ( @@ -1257,6 +1260,7 @@ function CustomNode({ id, data }) { widgetValues={data.widgetValues} onChange={ctx.onWidgetChange} openFileBrowser={ctx.openFileBrowser} + measurementChoices={nodeTableMeasurementChoices} /> )}
@@ -1307,29 +1311,46 @@ function CustomNode({ id, data }) { )} {/* Collapsible preview image */} - {data.previewImage - && !hidePreviewForInteractiveMask - && !(hasInteractiveLineOverlay && typeof data.previewImage === 'object' && data.previewImage.kind === 'line_plot') && ( - - - {typeof data.previewImage === 'string' ? ( -
- preview -
- ) : data.previewImage.kind === 'layer_gallery' ? ( - - ) : data.previewImage.kind === 'line_plot' ? ( - - ) : null} -
-
+ {data.previewImage && !hidePreviewForInteractiveMask && ( + typeof data.previewImage === 'object' && data.previewImage.kind === 'panels' + ? data.previewImage.panels.map((panel, pi) => ( + + + {panel.kind === 'line_plot' ? ( + + ) : panel.kind === 'image' ? ( +
+ {panel.title +
+ ) : null} +
+
+ )) + : !(hasInteractiveLineOverlay && typeof data.previewImage === 'object' && data.previewImage.kind === 'line_plot') && ( + + + {typeof data.previewImage === 'string' ? ( +
+ preview +
+ ) : data.previewImage.kind === 'layer_gallery' ? ( + + ) : data.previewImage.kind === 'line_plot' ? ( + + ) : null} +
+
+ ) )} {/* Interactive cross-section overlay */} @@ -1452,7 +1473,7 @@ function CustomNode({ id, data }) { // ── Widget renderer ─────────────────────────────────────────────────── -function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFileBrowser, hideLabel = false }) { +function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFileBrowser, hideLabel = false, measurementChoices }) { const { name, type, opts } = widget; const label = formatUiLabel(opts?.label || name); const val = value ?? opts?.default ?? ''; @@ -1484,12 +1505,9 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile const dynamicMeasurementChoices = useStore( useCallback( (s) => { - const measurementInputName = opts?.choices_from_measure_input; - if (!measurementInputName) return []; - const sourceType = getSourceTypeForInput(s, nodeId, measurementInputName); - if (sourceType !== 'RECORD_TABLE') return []; - const sourceNode = getSourceNodeForInput(s, nodeId, measurementInputName); - const rows = sourceNode?.data?.tableRows; + if (!opts?.choices_from_measure_input) return []; + const node = s.nodeLookup?.get(nodeId) || s.nodes?.find((n) => n.id === nodeId); + const rows = node?.data?.tableRows; return Array.isArray(rows) ? getMeasurementChoices(rows) : []; }, [nodeId, opts?.choices_from_measure_input],