work on curvature node
This commit is contained in:
@@ -3,7 +3,7 @@ from backend.nodes import (
|
|||||||
# IO
|
# IO
|
||||||
colormap,
|
colormap,
|
||||||
crop_resize,
|
crop_resize,
|
||||||
fft_2d_invert,
|
fft_2d_inverse,
|
||||||
filter_fft_1d,
|
filter_fft_1d,
|
||||||
filter_fft_2d,
|
filter_fft_2d,
|
||||||
filter_gaussian,
|
filter_gaussian,
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ def _compute_curvature_results(
|
|||||||
x0 = float(xc / q + 0.5 * xreal + field.xoff)
|
x0 = float(xc / q + 0.5 * xreal + field.xoff)
|
||||||
y0 = float(yc / q + 0.5 * yreal + field.yoff)
|
y0 = float(yc / q + 0.5 * yreal + field.yoff)
|
||||||
|
|
||||||
|
print(f"debug: {x0}, {y0}, {r1}, {r2}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"degree": float(degree),
|
"degree": float(degree),
|
||||||
"x0": x0,
|
"x0": x0,
|
||||||
@@ -290,8 +292,8 @@ class Curvature:
|
|||||||
OUTPUTS = (
|
OUTPUTS = (
|
||||||
('ANNOTATION_SOURCE', 'output'),
|
('ANNOTATION_SOURCE', 'output'),
|
||||||
('RECORD_TABLE', 'measurements'),
|
('RECORD_TABLE', 'measurements'),
|
||||||
('LINE', 'profile_1'),
|
('LINE', 'profile_x'),
|
||||||
('LINE', 'profile_2'),
|
('LINE', 'profile_y'),
|
||||||
)
|
)
|
||||||
FUNCTION = "process"
|
FUNCTION = "process"
|
||||||
|
|
||||||
@@ -338,7 +340,7 @@ class Curvature:
|
|||||||
|
|
||||||
profiles = []
|
profiles = []
|
||||||
for pair in intersections[:2]:
|
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:
|
while len(profiles) < 2:
|
||||||
profiles.append(_empty_profile(field.si_unit_xy, field.si_unit_z))
|
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])
|
output = field.replace(overlays=[*field.overlays, markup_spec])
|
||||||
|
|
||||||
table = RecordTable([
|
table = RecordTable([
|
||||||
{"quantity": "Center x position", "value": float(results["x0"]), "unit": field.si_unit_xy},
|
{"quantity": "Curvature radius 1", "value": results["r1"], "unit": field.si_unit_xy},
|
||||||
{"quantity": "Center y position", "value": float(results["y0"]), "unit": field.si_unit_xy},
|
{"quantity": "Curvature radius 2", "value": results["r2"], "unit": field.si_unit_xy},
|
||||||
{"quantity": "Center value", "value": float(results["z0"]), "unit": field.si_unit_z},
|
{"quantity": "Center x position", "value": results["x0"], "unit": field.si_unit_xy},
|
||||||
{"quantity": "Curvature radius 1", "value": float(results["r1"]), "unit": field.si_unit_xy},
|
{"quantity": "Center y position", "value": results["y0"], "unit": field.si_unit_xy},
|
||||||
{"quantity": "Curvature radius 2", "value": float(results["r2"]), "unit": field.si_unit_xy},
|
{"quantity": "Center value", "value": results["z0"], "unit": field.si_unit_z},
|
||||||
{"quantity": "Direction 1", "value": float(np.degrees(results["phi1"])), "unit": "deg"},
|
{"quantity": "Direction 1", "value": results["phi1"], "unit": "deg"},
|
||||||
{"quantity": "Direction 2", "value": float(np.degrees(results["phi2"])), "unit": "deg"},
|
{"quantity": "Direction 2", "value": results["phi2"], "unit": "deg"},
|
||||||
])
|
])
|
||||||
|
|
||||||
preview_base = render_datafield_preview(field, field.colormap)
|
preview_base = render_datafield_preview(field, field.colormap)
|
||||||
emit_preview(encode_preview(_apply_markup_overlay(preview_base, field, markup_spec)))
|
panels = []
|
||||||
emit_table(table)
|
|
||||||
|
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:
|
if warnings:
|
||||||
emit_warning(warnings[0])
|
emit_warning(warnings[0])
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ class Histogram:
|
|||||||
})
|
})
|
||||||
|
|
||||||
table = RecordTable([
|
table = RecordTable([
|
||||||
{"quantity": "delta Y", "value": yb - ya, "unit": count_unit},
|
|
||||||
{"quantity": "delta X", "value": xb - xa, "unit": field.si_unit_z},
|
{"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 position", "value": xa, "unit": field.si_unit_z},
|
||||||
{"quantity": "A count", "value": ya, "unit": count_unit},
|
{"quantity": "A count", "value": ya, "unit": count_unit},
|
||||||
{"quantity": "B position", "value": xb, "unit": field.si_unit_z},
|
{"quantity": "B position", "value": xb, "unit": field.si_unit_z},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from backend.node_registry import register_node
|
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.data_types import RecordTable
|
||||||
from backend.nodes.helpers import _measurement_entry, _measurement_value, _scalar_payload
|
from backend.nodes.helpers import _measurement_entry, _measurement_value, _scalar_payload
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ class ValueDisplay:
|
|||||||
"required": {
|
"required": {
|
||||||
"value": ("FLOAT", {
|
"value": ("FLOAT", {
|
||||||
"accepted_types": ["RECORD_TABLE"],
|
"accepted_types": ["RECORD_TABLE"],
|
||||||
|
"socket_only": True,
|
||||||
}),
|
}),
|
||||||
"measurement": ("STRING", {
|
"measurement": ("STRING", {
|
||||||
"default": "",
|
"default": "",
|
||||||
@@ -37,6 +38,7 @@ class ValueDisplay:
|
|||||||
def display_value(self, value, measurement: str = "") -> tuple:
|
def display_value(self, value, measurement: str = "") -> tuple:
|
||||||
unit = ""
|
unit = ""
|
||||||
if isinstance(value, RecordTable):
|
if isinstance(value, RecordTable):
|
||||||
|
emit_table(value)
|
||||||
row = _measurement_entry(value, measurement)
|
row = _measurement_entry(value, measurement)
|
||||||
numeric = _measurement_value(value, measurement)
|
numeric = _measurement_value(value, measurement)
|
||||||
unit = row.get("unit", "") if isinstance(row.get("unit"), str) else ""
|
unit = row.get("unit", "") if isinstance(row.get("unit"), str) else ""
|
||||||
|
|||||||
@@ -1056,6 +1056,9 @@ function CustomNode({ id, data }) {
|
|||||||
const combinedInputNames = new Set(combinedInputNameByWidgetName.values());
|
const combinedInputNames = new Set(combinedInputNameByWidgetName.values());
|
||||||
const renderedDataInputs = dataInputs.filter((input) => !combinedInputNames.has(input.name));
|
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 inlineWidgetsByInput = new Map();
|
||||||
const topWidgets = [];
|
const topWidgets = [];
|
||||||
const standaloneWidgets = [];
|
const standaloneWidgets = [];
|
||||||
@@ -1153,6 +1156,7 @@ function CustomNode({ id, data }) {
|
|||||||
widgetValues={data.widgetValues}
|
widgetValues={data.widgetValues}
|
||||||
onChange={ctx.onWidgetChange}
|
onChange={ctx.onWidgetChange}
|
||||||
openFileBrowser={ctx.openFileBrowser}
|
openFileBrowser={ctx.openFileBrowser}
|
||||||
|
measurementChoices={nodeTableMeasurementChoices}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1219,7 +1223,6 @@ function CustomNode({ id, data }) {
|
|||||||
|
|
||||||
{scalarDisplay && (
|
{scalarDisplay && (
|
||||||
<div className="node-value-display">
|
<div className="node-value-display">
|
||||||
<div className="node-value-label">Value</div>
|
|
||||||
<div className="node-value-box">
|
<div className="node-value-box">
|
||||||
<span className="node-value-box-number">{scalarDisplay.valueText}</span>
|
<span className="node-value-box-number">{scalarDisplay.valueText}</span>
|
||||||
{scalarDisplay.unitText && (
|
{scalarDisplay.unitText && (
|
||||||
@@ -1257,6 +1260,7 @@ function CustomNode({ id, data }) {
|
|||||||
widgetValues={data.widgetValues}
|
widgetValues={data.widgetValues}
|
||||||
onChange={ctx.onWidgetChange}
|
onChange={ctx.onWidgetChange}
|
||||||
openFileBrowser={ctx.openFileBrowser}
|
openFileBrowser={ctx.openFileBrowser}
|
||||||
|
measurementChoices={nodeTableMeasurementChoices}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1307,29 +1311,46 @@ function CustomNode({ id, data }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Collapsible preview image */}
|
{/* Collapsible preview image */}
|
||||||
{data.previewImage
|
{data.previewImage && !hidePreviewForInteractiveMask && (
|
||||||
&& !hidePreviewForInteractiveMask
|
typeof data.previewImage === 'object' && data.previewImage.kind === 'panels'
|
||||||
&& !(hasInteractiveLineOverlay && typeof data.previewImage === 'object' && data.previewImage.kind === 'line_plot') && (
|
? data.previewImage.panels.map((panel, pi) => (
|
||||||
<CollapsibleSection title="Preview" defaultOpen={true}>
|
<CollapsibleSection key={pi} title={panel.title || 'Preview'} defaultOpen={true}>
|
||||||
<PreviewBoundary
|
<PreviewBoundary
|
||||||
resetKey={typeof data.previewImage === 'string' ? data.previewImage : JSON.stringify({
|
resetKey={JSON.stringify({ kind: panel.kind, title: panel.title, len: panel.line?.length })}
|
||||||
kind: data.previewImage.kind,
|
fallbackImage={panel.fallback_image ?? null}
|
||||||
len: data.previewImage.line?.length,
|
>
|
||||||
layers: data.previewImage.layers?.length,
|
{panel.kind === 'line_plot' ? (
|
||||||
})}
|
<LinePlotOverlay overlay={panel} interactive={false} />
|
||||||
fallbackImage={typeof data.previewImage === 'object' ? data.previewImage.fallback_image : null}
|
) : panel.kind === 'image' ? (
|
||||||
>
|
<div className="node-preview">
|
||||||
{typeof data.previewImage === 'string' ? (
|
<img src={panel.image} alt={panel.title || 'preview'} draggable={false} />
|
||||||
<div className="node-preview">
|
</div>
|
||||||
<img src={data.previewImage} alt="preview" draggable={false} />
|
) : null}
|
||||||
</div>
|
</PreviewBoundary>
|
||||||
) : data.previewImage.kind === 'layer_gallery' ? (
|
</CollapsibleSection>
|
||||||
<LayerGalleryPreview overlay={data.previewImage} />
|
))
|
||||||
) : data.previewImage.kind === 'line_plot' ? (
|
: !(hasInteractiveLineOverlay && typeof data.previewImage === 'object' && data.previewImage.kind === 'line_plot') && (
|
||||||
<LinePlotOverlay overlay={data.previewImage} interactive={false} />
|
<CollapsibleSection title="Preview" defaultOpen={true}>
|
||||||
) : null}
|
<PreviewBoundary
|
||||||
</PreviewBoundary>
|
resetKey={typeof data.previewImage === 'string' ? data.previewImage : JSON.stringify({
|
||||||
</CollapsibleSection>
|
kind: data.previewImage.kind,
|
||||||
|
len: data.previewImage.line?.length,
|
||||||
|
layers: data.previewImage.layers?.length,
|
||||||
|
})}
|
||||||
|
fallbackImage={typeof data.previewImage === 'object' ? data.previewImage.fallback_image : null}
|
||||||
|
>
|
||||||
|
{typeof data.previewImage === 'string' ? (
|
||||||
|
<div className="node-preview">
|
||||||
|
<img src={data.previewImage} alt="preview" draggable={false} />
|
||||||
|
</div>
|
||||||
|
) : data.previewImage.kind === 'layer_gallery' ? (
|
||||||
|
<LayerGalleryPreview overlay={data.previewImage} />
|
||||||
|
) : data.previewImage.kind === 'line_plot' ? (
|
||||||
|
<LinePlotOverlay overlay={data.previewImage} interactive={false} />
|
||||||
|
) : null}
|
||||||
|
</PreviewBoundary>
|
||||||
|
</CollapsibleSection>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Interactive cross-section overlay */}
|
{/* Interactive cross-section overlay */}
|
||||||
@@ -1452,7 +1473,7 @@ function CustomNode({ id, data }) {
|
|||||||
|
|
||||||
// ── Widget renderer ───────────────────────────────────────────────────
|
// ── 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 { name, type, opts } = widget;
|
||||||
const label = formatUiLabel(opts?.label || name);
|
const label = formatUiLabel(opts?.label || name);
|
||||||
const val = value ?? opts?.default ?? '';
|
const val = value ?? opts?.default ?? '';
|
||||||
@@ -1484,12 +1505,9 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile
|
|||||||
const dynamicMeasurementChoices = useStore(
|
const dynamicMeasurementChoices = useStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(s) => {
|
(s) => {
|
||||||
const measurementInputName = opts?.choices_from_measure_input;
|
if (!opts?.choices_from_measure_input) return [];
|
||||||
if (!measurementInputName) return [];
|
const node = s.nodeLookup?.get(nodeId) || s.nodes?.find((n) => n.id === nodeId);
|
||||||
const sourceType = getSourceTypeForInput(s, nodeId, measurementInputName);
|
const rows = node?.data?.tableRows;
|
||||||
if (sourceType !== 'RECORD_TABLE') return [];
|
|
||||||
const sourceNode = getSourceNodeForInput(s, nodeId, measurementInputName);
|
|
||||||
const rows = sourceNode?.data?.tableRows;
|
|
||||||
return Array.isArray(rows) ? getMeasurementChoices(rows) : [];
|
return Array.isArray(rows) ? getMeasurementChoices(rows) : [];
|
||||||
},
|
},
|
||||||
[nodeId, opts?.choices_from_measure_input],
|
[nodeId, opts?.choices_from_measure_input],
|
||||||
|
|||||||
Reference in New Issue
Block a user