function DenoisedPanel({ denoisedImage, characterData, style }){
const imgRef = React.useRef(null);
const canvasRef = React.useRef(null);
const drawBoxes = () => {
const img = imgRef.current;
const canvas = canvasRef.current;
if (!img || !canvas) return;
const pad = 8;
const cW = img.offsetWidth;
const cH = img.offsetHeight;
canvas.width = cW;
canvas.height = cH;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, cW, cH);
if (!characterData || characterData.length === 0) return;
const nW = img.naturalWidth;
const nH = img.naturalHeight;
if (!nW || !nH) return;
const availW = cW - pad * 2;
const availH = cH - pad * 2;
const scale = Math.min(availW / nW, availH / nH);
const rendW = nW * scale;
const rendH = nH * scale;
const offX = pad + (availW - rendW) / 2;
const offY = pad + (availH - rendH) / 2;
ctx.strokeStyle = "rgba(0,200,160,0.85)";
ctx.lineWidth = 1.5;
for (const item of characterData){
const [x, y, w, h] = item.bbox;
ctx.strokeRect(offX + x * scale, offY + y * scale, w * scale, h * scale);
}
};
React.useEffect(() => {
const img = imgRef.current;
if (!img) return;
if (img.complete && img.naturalWidth) drawBoxes();
else img.onload = drawBoxes;
}, [denoisedImage, characterData]);
return (
Huffman tree · input: "{recognized}"
{mode === "steps" && steps && (
{ setPlaying(false); setStepIdx(Number(e.target.value)); }}
style={{ flex: 1, accentColor: "rgba(0,180,140,1)" }} />
step {stepIdx + 1} / {steps.length}
)}
{mode === "steps" && steps && currentStep && (
{currentStep.is_new ? "new symbol" : "seen before"}
'{currentStep.char}'
→ code: {codeMap[currentStep.char] || "—"}
{currentStep.swaps && currentStep.swaps.length > 0 && (
↔ swapped during this step: {currentStep.swaps.map(([a,b]) => `#${a} ↔ #${b}`).join(", ")}
)}
)}
);
}
const STAGE_DEFS = [
{ n: "1", id: "ocr", name: "OCR", desc: "CNN → text", endpoint: "$STAGE1_URL/predict" },
{ n: "2", id: "encode", name: "Compress", desc: "Adaptive Huffman", endpoint: "$STAGE2_URL/encode" },
{ n: "3", id: "decode", name: "Decompress", desc: "Lossless verify", endpoint: "$STAGE2_URL/decode" },
];
function fmtMs(seconds){
if (seconds == null) return "—";
return `${(seconds*1000).toFixed(1)} ms`;
}
function StepperView({ stage, progress, result, sample, onViewResults }){
const [expandedOcr, setExpandedOcr] = React.useState(false);
const [expandedCompress, setExpandedCompress] = React.useState(false);
const ocrClickable = result && result.denoisedImage;
const compressClickable = result && result.recognized;
const decompressClickable = result && result.recovered;
return (
{STAGE_DEFS.map((s, i) => {
const active = stage === i + 1;
const done = stage > i + 1 || (stage === 4);
const p = progress[i] || 0;
const stateLabel = done ? "COMPLETE" : active ? "RUNNING" : "IDLE";
const latencyKey = ["ocrLatency","compLatency","decLatency"][i];
const isOcr = i === 0;
const isCompress = i === 1;
const isDecompress = i === 2;
const highlighted = (isOcr && expandedOcr) || (isCompress && expandedCompress);
const clickable = (isOcr && ocrClickable) || (isCompress && compressClickable) || (isDecompress && decompressClickable);
const onClick = isOcr && ocrClickable ? () => { setExpandedOcr(v => !v); setExpandedCompress(false); }
: isCompress && compressClickable ? () => { setExpandedCompress(v => !v); setExpandedOcr(false); }
: isDecompress && decompressClickable ? () => onViewResults && onViewResults()
: undefined;
const expanded = isOcr ? expandedOcr : isCompress ? expandedCompress : false;
return (
STEP {s.n}{clickable ? {expanded ? "▲ hide" : "▼ view output"} : null}
{s.name}
{s.desc}
{stateLabel}
{result ? fmtMs(result[latencyKey]) : "— ms"}
);
})}
{expandedOcr && result && result.denoisedImage && (
step 0 · raw
{sample && sample.src
?

: sample && sample.imageBase64
?

: null
}
Extracted text
{result.recognized}
)}
{expandedCompress && result && result.recognized && (
)}
);
}
function NetworkView({ stage, progress, result }){
const positions = [
{ id: "in", x: 10, y: 50, label: "INPUT", sub: "image/png", kind: "io" },
{ id: "ocr", x: 30, y: 50, label: "STAGE 01", sub: "CNN · OCR", stageIdx: 1 },
{ id: "enc", x: 52, y: 30, label: "STAGE 02A", sub: "Huffman · encode", stageIdx: 2 },
{ id: "dec", x: 74, y: 70, label: "STAGE 02B", sub: "Huffman · decode", stageIdx: 3 },
{ id: "out", x: 93, y: 50, label: "VERIFY", sub: "lossless", kind: "io" },
];
const edges = [
["in","ocr"],["ocr","enc"],["enc","dec"],["dec","out"]
];
return (