Same "don't materialize the full N-fold representation" class as #731, for images.
Most multi-channel composite branches (render.py:2006, 2034, 2042, 2100, 2116) do np.stack([cmap(layers[ch]) for ch in channels], 0).sum(0) / n, holding O(n_channels) full (H,W,4) float64 buffers + the stacked (N,H,W,4) cube simultaneously (peak ~64·N·H·W bytes; GB-scale on large multiplex images). layers also retains every channel's normalized full-res float array at once.
One branch (render.py:2067) already proves the fix: a single (H,W,3) accumulator with comp_rgb += ... in a loop — O(1) full buffers. Apply the same streaming accumulate to the other branches (and fold per-channel normalization into the loop so only one channel is live).
Caveat: this touches the blending math (the documented /n_channels averaging design) — must produce byte-identical output, verified main-vs-branch, not just refactor the allocation.
Same "don't materialize the full N-fold representation" class as #731, for images.
Most multi-channel composite branches (
render.py:2006,2034,2042,2100,2116) donp.stack([cmap(layers[ch]) for ch in channels], 0).sum(0) / n, holding O(n_channels) full(H,W,4)float64 buffers + the stacked(N,H,W,4)cube simultaneously (peak ~64·N·H·Wbytes; GB-scale on large multiplex images).layersalso retains every channel's normalized full-res float array at once.One branch (
render.py:2067) already proves the fix: a single(H,W,3)accumulator withcomp_rgb += ...in a loop — O(1) full buffers. Apply the same streaming accumulate to the other branches (and fold per-channel normalization into the loop so only one channel is live).Caveat: this touches the blending math (the documented
/n_channelsaveraging design) — must produce byte-identical output, verified main-vs-branch, not just refactor the allocation.