lapstack

Alignment

Frames in a stack never line up exactly. Changing focus changes magnification — the image breathes — and a rail moves the camera, which changes perspective as well as scale. Before anything can be compared pixel to pixel, the frames must be registered.

lapstack registers each frame against the previous aligned frame, chaining the whole stack back to frame 0. The registration is direct and intensity-based: it works coarse-to-fine on a Gaussian pyramid of the luminance, minimizing a DC-removed RMS difference with a bounded Nelder–Mead search.

Note

If your frames are already registered — a microscope stack, or frames you aligned elsewhere — turn alignment off (align frames, or --no-align). The run then streams the frames straight from disk and skips what is normally most of the time.

The model

model in the panel, --align-model on the command line: how much a frame is allowed to be deformed to land on the previous one.

ModelDegrees of freedomWhen
similarity (default)Shift, scale, rotationWhat a focus rail or a focus ring actually produces. This is also Zerene's and Helicon's model.
affine+ aspect ratio, shearRarely needed; use when similarity leaves visible misregistration
projective+ the two perspective termsA camera that tilted against the subject as it stepped, so the frames keystone

Whichever model you choose, the transform is one 3×3 homography — the perspective row is simply [0, 0, 1] below projective — so every warp, the crop and the brightness sampling read pixels through the same map, and similarity results are unchanged to the bit.

More parameters take longer to search, and on a stack that does not need them the extra ones only fit noise. Leave it on similarity unless you have a reason.

On a frame keystoned by a known perspective distortion, the projective fit brings it back to an RMS error of 0.0029 of full scale — the floor set by resampling itself — where similarity leaves 0.0076 and affine 0.0071. On a pure scale and rotation, which similarity recovers exactly, the floor is 0.0028. In other words: use the model your capture actually needs, and nothing more.

You can also restrict the model from the other direction. On the command line, --no-shift, --no-scale and --no-rotation each remove one degree of freedom.

Coarsening

align coarsen in the panel, --align-coarsen N on the command line. The browser and desktop app default to 2; the command line defaults to 0, so pass --align-coarsen 2 there to get the same behavior.

The registration works down a pyramid. Coarsening stops it N levels short of full resolution. Because the transform is resolution-independent, this is a large speed-up at essentially no cost in accuracy — the search at each level starts one pixel of that level wide and stops at a tenth of one, so the finest level searched still settles sub-pixel.

A value of 2 is right for almost everything. Lower it only if you can see misregistration in the result; raise it for a faster draft.

Resampling kernel

interpolation in the panel, --interpolation on the command line: the kernel each aligned frame is resampled with once its transform has been found. This is the same choice Zerene and Helicon offer.

KernelCharacter
nearestThe nearest source pixel. Nothing blurred, nothing rung, sub-pixel shifts land jagged. For stacks already aligned to the pixel, or to see the pixels as shot.
bilinearSoft
bicubicKeys' cubic convolution
spline4x4Default. Panorama Tools' spline16, and Zerene's default
spline6x6Sharper, a little ringing at hard edges
lanczos3Sharpest, most ringing

All are separable and interpolating: a pixel-centered sample comes back unchanged and the weights sum to one. Wider kernels are sharper and ring more at hard edges.

The registration search itself always resamples with spline4x4, regardless of this setting — the fit does not depend on the kernel, so the choice changes only how the frames are finally read, not where they land.

Brightness equalization

equalize brightness in the panel, on by default; --no-brightness turns it off.

Flash recycling, mains-powered lights and a shutter that is not quite repeatable make frames differ in exposure by a percent or two. The region-energy rule sees it: energy grows with the square of the gain, so a brighter frame wins ties it should not, and the seams between winners show as patches.

lapstack brings every frame to frame 0's brightness with one gain per channel — the ratio of the two frames' channel means over the pixels the frame's warp covers. Means rather than a pixel-wise fit, because a blur leaves a mean alone while a pixel-wise fit would slope toward zero with the defocus. Per channel, so a light that flickers in color is corrected too.

Gains are clamped to the range ¼ to 4 and logged, and each filmstrip entry shows its own.

The automatic crop

Where a warped frame does not reach, the warp repeats its edge — so those border pixels are not real. Every output is therefore cropped to the largest rectangle that every frame covers with real pixels, and the interpolation kernel's own support is kept out of it (nearest 0 px, bilinear 1, the four-tap kernels 2, the six-tap kernels 3), so a wider kernel loses a pixel or two more at the border.

The run reports the crop window. In the browser, the viewer shows it as the bright part of the image and the Save step's Crop section has the switch. On the command line, --no-crop keeps the full frame.

You can crop further by hand: Crop (C) in the viewer arms a drag, and the rectangle you draw becomes the run's crop window, applied to every file the Save step writes and kept in a project file. The command line's equivalent is --crop X,Y,W,H, in full-resolution pixels of the turned frame.

What alignment costs

Alignment is normally the largest part of a frame's time. Some numbers, on 45-megapixel frames:

  • About 300 cost evaluations for a pair at coarsen 2.
  • Each evaluation is one row-parallel pass that warps and reduces at once, with nothing allocated: 1.6 ms at pyramid level 2, 20 ms at full resolution.
  • On the CPU path this is about 0.2 s of a frame's 0.75 s.

The stack is streamed through alignment: each frame is decoded, registered against the previous aligned frame, warped, brought to frame 0's brightness, folded into the fusion, and dropped. Only the transforms, the gains and frame 0's brightness grid are kept. This is why memory stays flat however long the stack — before streaming, a 100-frame 45-megapixel stack needed over 100 GB resident.

With --gpu-align (native) or in the browser, the search runs on the GPU. See performance.

Source: sites/lapstack/content/docs/app/alignment.md