NC Imago · Docs

Photo Merge (HDR, Panorama, HDR Panorama)

crates/merge (layer L3, no UI dependencies) implements the algorithms; crates/engine/src/merge.rs plans, runs and finishes merges in the library; the desktop UI shows a dialog with a live preview. All algorithms are our own implementations from published papers (cited in the module docs).

Using it

What it does

HDR (hdr.rs): frames ordered by EXIF exposure (or median luminance); the middle one is the reference. Auto Align: features on exposure-normalised luminance, RANSAC translation / similarity / homography (the simplest model that explains ≥ 97 % of the matches). Frames are resampled with Lanczos-3, held to the range of their 4 × 4 neighbours so edges don't ring (imago_raster::sample); bilinear lost a third of the finest detail. Exposure ratios are measured (median log ratio of well-exposed pixels between brightness neighbours), EXIF is the fallback. Radiance is a weighted mean in linear light, weight k · (1 − smoothstep(0.8·clip, 0.96·clip, max channel within 1 px)): a pixel next to clipped photosites was demosaiced from them and reads low in the clipped channel, so it goes with them (on real ARW brackets the brightest frame's green was 3–5 % low there, and bright windows took a magenta tinge). The frames are taken in brightness order (ties by content), so the result doesn't depend on the order they come in. Where the reference is at or near its clip the scene is at least that bright, so a frame that reads far darker there (an object moved: a star, the moon, a passing light) is left out and the clipped highlight stays (always on: a moving star otherwise left a black hole). Deghost: a per-pixel consistency test on ≤ 2048 px versions against the reference radiance (with a noise floor; the reference predicts down to 0.1 % of its clip, so a brighter frame's ghost in a night sky is caught), dilated and feathered masks; the union is the deghost overlay. Output: 16-bit float DNG. Lens corrections: frames are merged uncorrected, and the DNG's OpcodeList3 carries the reference frame's (WarpRectilinear, WarpRectilinear2, FixVignetteRadial: a DNG's own, or a camera's correction a raw reader converts, e.g. Sony, Panasonic), re-expressed for its default crop (Opcode::cropped). So Optics ▸ Enable Lens Corrections works on the result as on the sources; a vertical bracket keeps its pixels unturned and its corrections.

Panorama (pano/): MOPS-style features, pairwise RANSAC homographies with Brown & Lowe's verification, focal from homographies (or EXIF 35 mm), rotations chained along a maximum spanning tree, Levenberg–Marquardt bundle adjustment of rotations + focal lengths, straightening. Projections: spherical, cylindrical, perspective; Auto picks perspective for compact fields of view < 110°, cylindrical when the vertical field is < 90°, else spherical. Exposure: EXIF, then gain compensation from the overlaps. Seams: each pixel goes to the image with the highest centre-weighted feather weight; multi-band blending (Laplacian pyramids) in log space. Boundary Warp: a separable stretch of the smoothed valid-area edges to the canvas edges. Auto Crop: the largest rectangle of image data (set as the develop crop, so it stays adjustable). Fill Edges: push–pull diffusion. Output: 16-bit integer DNG (16-bit float for HDR panoramas).

DNG output (output.rs): LinearRaw, 3 samples, Deflate tiles; values scaled below 1.0 with the scale in BaselineExposure; raw sources keep their DNG colour tags (camera RGB), other sources get ColorMatrix1/ForwardMatrix1 of their RGB space.

Measured (synthetic tests, cargo test -p imago-merge)

Limits (known gaps)