From 3ff65a93ef5e6de770505866de13867607bb6d09 Mon Sep 17 00:00:00 2001 From: mitchellhansen Date: Sun, 26 Apr 2026 21:24:25 -0700 Subject: [PATCH] fix: weight slider, Output node preview, blend_mode serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src-frontend/src/App.jsx | 1 - src-frontend/src/components/NodeGraph.jsx | 17 ++++++++--------- src/detect.rs | 19 +++++++++++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src-frontend/src/App.jsx b/src-frontend/src/App.jsx index 5b056539..958c80a0 100644 --- a/src-frontend/src/App.jsx +++ b/src-frontend/src/App.jsx @@ -417,7 +417,6 @@ export default function App() { }} nodePreviews={passes[activePass].nodePreviews} sourceImageB64={image?.preview_b64 ?? null} - outputImageB64={passes[activePass]?.vizB64 ?? null} /> ) : (
{BLEND_MODES.map(m => ( - ))} diff --git a/src/detect.rs b/src/detect.rs index 507154e3..bf98e79c 100644 --- a/src/detect.rs +++ b/src/detect.rs @@ -505,7 +505,18 @@ pub fn evaluate_graph( let node = node_map[id]; let result: Option> = 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> = 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();