fix value display node
This commit is contained in:
@@ -43,10 +43,9 @@ from backend.nodes import (
|
||||
markup,
|
||||
preview_image,
|
||||
statistics,
|
||||
value_io,
|
||||
view_3d,
|
||||
print_table,
|
||||
value_display,
|
||||
# Analysis
|
||||
curvature,
|
||||
fractal_dimension,
|
||||
histogram,
|
||||
|
||||
@@ -154,13 +154,12 @@ def _compute_curvature_results(
|
||||
+ coeffs[5] * y_norm * y_norm
|
||||
)
|
||||
|
||||
r1 = float("inf") if abs(kappa1) <= 1e-14 else float(1.0 / (q * q * kappa1))
|
||||
r2 = float("inf") if abs(kappa2) <= 1e-14 else float(1.0 / (q * q * kappa2))
|
||||
#todo: fix inf case
|
||||
r1 = float(np.inf) if abs(kappa1) <= 1e-14 else float(1.0 / (q * q * kappa1))
|
||||
r2 = float(np.inf) if abs(kappa2) <= 1e-14 else float(1.0 / (q * q * kappa2))
|
||||
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,
|
||||
@@ -292,8 +291,8 @@ class Curvature:
|
||||
OUTPUTS = (
|
||||
('ANNOTATION_SOURCE', 'output'),
|
||||
('RECORD_TABLE', 'measurements'),
|
||||
('LINE', 'profile_x'),
|
||||
('LINE', 'profile_y'),
|
||||
('LINE', 'profile_a'),
|
||||
('LINE', 'profile_b'),
|
||||
)
|
||||
FUNCTION = "process"
|
||||
|
||||
@@ -340,7 +339,7 @@ class Curvature:
|
||||
|
||||
profiles = []
|
||||
for pair in intersections[:2]:
|
||||
profiles.append(_profile_from_intersections(field, pair[1], pair[0]))
|
||||
profiles.append(_profile_from_intersections(field, pair[0], pair[1]))
|
||||
while len(profiles) < 2:
|
||||
profiles.append(_empty_profile(field.si_unit_xy, field.si_unit_z))
|
||||
|
||||
@@ -360,7 +359,7 @@ class Curvature:
|
||||
preview_base = render_datafield_preview(field, field.colormap)
|
||||
panels = []
|
||||
|
||||
for p, title in zip(profiles, ["X Principal Axis", "Y Principal Axis"]):
|
||||
for p, title in zip(profiles, ["Principal Axis A", "Principal Axis B"]):
|
||||
if len(p.data) > 0:
|
||||
panels.append({
|
||||
"title": title,
|
||||
@@ -376,7 +375,7 @@ class Curvature:
|
||||
})
|
||||
|
||||
emit_preview({"kind": "panels", "panels": panels})
|
||||
# emit_table(table)
|
||||
emit_table(table)
|
||||
|
||||
if warnings:
|
||||
emit_warning(warnings[0])
|
||||
|
||||
@@ -4,6 +4,7 @@ Shared helper functions for argonode nodes.
|
||||
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
@@ -16,6 +17,62 @@ from backend.runtime_paths import demo_dir, input_dir, output_dir
|
||||
# Scalar payload helpers (from display.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SI_PREFIXES: dict[str, float] = {
|
||||
'Y': 1e24, 'Z': 1e21, 'E': 1e18, 'P': 1e15, 'T': 1e12,
|
||||
'G': 1e9, 'M': 1e6, 'k': 1e3,
|
||||
'm': 1e-3, 'u': 1e-6, 'µ': 1e-6, 'n': 1e-9, 'p': 1e-12,
|
||||
'f': 1e-15, 'a': 1e-18, 'z': 1e-21, 'y': 1e-24,
|
||||
}
|
||||
|
||||
_PREFIXABLE_UNITS: frozenset[str] = frozenset({
|
||||
'm', 's', 'A', 'V', 'W', 'Hz', 'F', 'C', 'J', 'N', 'Pa',
|
||||
'T', 'H', 'S', 'g', 'K', 'Ohm', 'ohm', 'Ω',
|
||||
})
|
||||
|
||||
_NUMBER_RE = re.compile(
|
||||
r'^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(.*)?$'
|
||||
)
|
||||
|
||||
|
||||
def parse_number_with_unit(text: str) -> tuple[float, str]:
|
||||
"""Parse a string like '1.5 nm' into (1.5e-9, 'm').
|
||||
|
||||
The numeric part may use scientific notation. The unit is stripped of any
|
||||
recognised SI prefix and the raw value is scaled accordingly, so the
|
||||
returned float is always in the base SI unit. Units that are not
|
||||
prefixable are returned unchanged alongside the unscaled value.
|
||||
|
||||
Examples::
|
||||
|
||||
parse_number_with_unit("1 um") → (1e-6, "m")
|
||||
parse_number_with_unit("500 nm") → (5e-7, "m")
|
||||
parse_number_with_unit("3.14") → (3.14, "")
|
||||
parse_number_with_unit("2 kHz") → (2000.0, "Hz")
|
||||
"""
|
||||
text = text.strip()
|
||||
if not text:
|
||||
return 0.0, ""
|
||||
|
||||
m = _NUMBER_RE.match(text)
|
||||
if not m:
|
||||
raise ValueError(f"Cannot parse number: {text!r}")
|
||||
|
||||
numeric = float(m.group(1))
|
||||
unit_str = (m.group(2) or "").strip()
|
||||
|
||||
if not unit_str:
|
||||
return numeric, ""
|
||||
|
||||
# Try prefix + base-unit split (handle multi-byte µ as a prefix)
|
||||
if len(unit_str) >= 2:
|
||||
prefix_char = unit_str[0]
|
||||
rest = unit_str[1:]
|
||||
if prefix_char in _SI_PREFIXES and rest in _PREFIXABLE_UNITS:
|
||||
return numeric * _SI_PREFIXES[prefix_char], rest
|
||||
|
||||
return numeric, unit_str
|
||||
|
||||
|
||||
def _scalar_payload(value: float, unit: str = "") -> dict:
|
||||
payload = {"value": float(value)}
|
||||
if isinstance(unit, str) and unit.strip():
|
||||
@@ -24,7 +81,7 @@ def _scalar_payload(value: float, unit: str = "") -> dict:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Measurement helpers (from display.py — used by ValueDisplay)
|
||||
# Measurement helpers (from display.py — used by ValueIO)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _measurement_names(table: list) -> list[str]:
|
||||
|
||||
@@ -2,18 +2,21 @@ from __future__ import annotations
|
||||
from backend.node_registry import register_node
|
||||
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
|
||||
from backend.nodes.helpers import _measurement_entry, _measurement_value, _scalar_payload, parse_number_with_unit
|
||||
|
||||
|
||||
@register_node(display_name="Value Display")
|
||||
class ValueDisplay:
|
||||
class ValueIO:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {
|
||||
"accepted_types": ["RECORD_TABLE"],
|
||||
"socket_only": True,
|
||||
"number_input": ("STRING", {
|
||||
"text_input": True,
|
||||
"default": "0",
|
||||
"placeholder": "e.g. 1.5 nm",
|
||||
"hide_when_input_connected": "value",
|
||||
"hide_label": True,
|
||||
}),
|
||||
"measurement": ("STRING", {
|
||||
"default": "",
|
||||
@@ -22,7 +25,13 @@ class ValueDisplay:
|
||||
"value": ["RECORD_TABLE"],
|
||||
},
|
||||
}),
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"value": ("FLOAT", {
|
||||
"accepted_types": ["RECORD_TABLE"],
|
||||
"socket_only": True,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
OUTPUTS = (
|
||||
@@ -35,14 +44,16 @@ class ValueDisplay:
|
||||
_broadcast_value_fn = None
|
||||
_current_node_id: str = ""
|
||||
|
||||
def display_value(self, value, measurement: str = "") -> tuple:
|
||||
def display_value(self, number_input: str = "0", value=None, 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 ""
|
||||
else:
|
||||
elif value is not None:
|
||||
numeric = float(value)
|
||||
else:
|
||||
numeric, unit = parse_number_with_unit(str(number_input))
|
||||
emit_value(_scalar_payload(numeric, unit))
|
||||
return (numeric,)
|
||||
Reference in New Issue
Block a user