fix: weight slider, Output node preview, blend_mode serialization
Three bugs fixed: 1. Weight slider had no effect — evaluate_graph called apply_layer but never applied layer.weight. Now applies the same lerp as the old apply_stack: effective = clamp(255*(1-w) + raw*w, 0, 255). w=0 → background, w=1 → identity, w>1 → amplified toward ink. 2. Output node showed thresholded binary (vizB64) instead of the raw response map. Root cause: Output was filtered out of node_previews, so NodeGraph fell back to outputImageB64 which is the post-threshold viz. Fix: include Output in node_previews (only Source is excluded); NodeGraph now uses nodePreviews[node.id] for all nodes except Source. The threshold slider in Hulls & Contours no longer affects detection graph thumbnails. 3. Combine blend_mode was always "Average" because the frontend stored the field as blendMode (camelCase) but the Rust payload expected blend_mode (snake_case) — serde found nothing and defaulted. Changed to snake_case throughout NodeGraph.jsx to match the rest of the payload convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -417,7 +417,6 @@ export default function App() {
|
||||
}}
|
||||
nodePreviews={passes[activePass].nodePreviews}
|
||||
sourceImageB64={image?.preview_b64 ?? null}
|
||||
outputImageB64={passes[activePass]?.vizB64 ?? null}
|
||||
/>
|
||||
) : (
|
||||
<Viewport
|
||||
|
||||
@@ -39,7 +39,7 @@ function bezier(from, to) {
|
||||
}
|
||||
|
||||
// ── Component ──────────────────────────────────────────────────────────────────
|
||||
export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB64, outputImageB64 }) {
|
||||
export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB64 }) {
|
||||
const canvasRef = useRef(null)
|
||||
const [pan, setPan] = useState({ x: 40, y: 40 })
|
||||
const [zoom, setZoom] = useState(1)
|
||||
@@ -190,7 +190,7 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
||||
const id = newNodeId(kind)
|
||||
const node = kind === 'Kernel'
|
||||
? { id, kind, x, y, ...defaultKernelProps() }
|
||||
: { id, kind, x, y, blendMode: 'Average', inputCount: 2 }
|
||||
: { id, kind, x, y, blend_mode: 'Average', inputCount: 2 }
|
||||
const g = graphRef.current
|
||||
onChangeRef.current({ ...g, nodes: [...g.nodes, node] })
|
||||
}
|
||||
@@ -226,10 +226,9 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
||||
: (node.kind === 'Kernel' || node.kind === 'Output') ? 1 : 0
|
||||
const hasOut = node.kind !== 'Output'
|
||||
|
||||
// Preview image: explicit source/output images, or per-node detection map
|
||||
const preview = node.kind === 'Source' ? sourceImageB64
|
||||
: node.kind === 'Output' ? outputImageB64
|
||||
: nodePreviews?.[node.id]
|
||||
// Source shows the original image; all other nodes (including Output)
|
||||
// show the raw response map from nodePreviews — consistent and threshold-independent.
|
||||
const preview = node.kind === 'Source' ? sourceImageB64 : nodePreviews?.[node.id]
|
||||
|
||||
const accentColor = node.kind === 'Source' ? '#7c3aed'
|
||||
: node.kind === 'Output' ? '#b45309'
|
||||
@@ -326,11 +325,11 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
||||
{node.kind === 'Combine' && (<>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
|
||||
{BLEND_MODES.map(m => (
|
||||
<button key={m} onMouseDown={e => e.stopPropagation()} onClick={() => updateNode(node.id, { blendMode: m })}
|
||||
<button key={m} onMouseDown={e => e.stopPropagation()} onClick={() => updateNode(node.id, { blend_mode: m })}
|
||||
style={{
|
||||
padding: '1px 5px', borderRadius: 3, fontSize: 10, cursor: 'pointer', border: 'none',
|
||||
background: node.blendMode === m ? '#0f766e' : '#1e293b',
|
||||
color: node.blendMode === m ? '#fff' : '#94a3b8',
|
||||
background: node.blend_mode === m ? '#0f766e' : '#1e293b',
|
||||
color: node.blend_mode === m ? '#fff' : '#94a3b8',
|
||||
}}
|
||||
>{m}</button>
|
||||
))}
|
||||
|
||||
@@ -505,7 +505,18 @@ pub fn evaluate_graph(
|
||||
let node = node_map[id];
|
||||
let result: Option<Vec<u8>> = match &node.kind {
|
||||
NodeKind::Source => None,
|
||||
NodeKind::Kernel(layer) => Some(apply_layer(rgb, layer)),
|
||||
NodeKind::Kernel(layer) => {
|
||||
let raw = apply_layer(rgb, layer);
|
||||
let w = layer.weight;
|
||||
// w=0 → full background, w=1 → identity, w>1 → amplify toward ink
|
||||
Some(if (w - 1.0).abs() < 1e-6 {
|
||||
raw
|
||||
} else {
|
||||
raw.iter().map(|&r| {
|
||||
(255.0 * (1.0 - w) + r as f32 * w).clamp(0.0, 255.0) as u8
|
||||
}).collect()
|
||||
})
|
||||
}
|
||||
NodeKind::Combine(mode) => {
|
||||
let mut ins = incoming[id].clone();
|
||||
ins.sort_by_key(|&(_, p)| p);
|
||||
@@ -530,12 +541,12 @@ pub fn evaluate_graph(
|
||||
.and_then(|n| outputs.get(n.id.as_str()).cloned())
|
||||
.unwrap_or_else(bg);
|
||||
|
||||
// Per-node previews — omit Source (no output) and Output (same as final_map)
|
||||
// Per-node previews — omit only Source (no map output); include Output so
|
||||
// the graph editor can show the raw response map (not the thresholded vizB64).
|
||||
let previews: HashMap<String, Vec<u8>> = outputs.into_iter()
|
||||
.filter(|(id, _)| {
|
||||
let key: &str = id;
|
||||
node_map.get(key).map_or(true, |n|
|
||||
!matches!(n.kind, NodeKind::Source | NodeKind::Output))
|
||||
node_map.get(key).map_or(true, |n| !matches!(n.kind, NodeKind::Source))
|
||||
})
|
||||
.map(|(id, map)| (id.to_string(), map))
|
||||
.collect();
|
||||
|
||||
Reference in New Issue
Block a user