match_pose op• Data kinds: voxel × voxel → shift
• Call: import fullseye as fs; fs.ledger.match_phase_3d(a, b, device='cpu', window='tukey', whitening=0.25) (to call the implementation directly, import match3d; match3d.match_phase_3d(a, b, device='cpu', window='tukey', whitening=0.25); from the registry, ops3d.get("match_phase_3d"))
• GPU: this op has a GPU path (device="cuda")
3-D phase correlation (FFT). Returns the integer shift (dz, dy, dx) that aligns b to a.
Reddy & Chatterji in 3D. The peak of the inverse FFT of the cross-power spectrum R = A·conj(B) divided by |R|^whitening = the translation. No template needed, whole volume, O(N log N). Rotation/scale are handled separately (PCA / log-polar).
Arguments: a, b are 3-D arrays of the same shape (otherwise ValueError). They are cast to float32 for the FFT. window: "tukey" (default, α 0.5), "hann", None (no window) —— the window is applied after subtracting the mean, and not on axes shorter than 8. whitening: exponent of |R| (0 to 1, 1 = full whitening of classic phase correlation, 0 = plain cross-correlation, default 0.25).
Returns: an int tuple (dz, dy, dx), each axis wrapped into (−N/2, N/2]. Meaning:
np.roll(b, (dz,dy,dx), axis=(0,1,2)) ≈ a (shifting b by this much puts it on top of a).
★Why the default changed (2026-10-06, the same trap as filters_freq.phase_correlation_fft): the "no window + full whitening (eps 1e-9)" used up to 0.4.0, on non-periodic crops of band-limited volumes (the ordinary use: 2 acquisitions that only partly overlap), gave frequency bins without signal (leakage from the discontinuity at the crop edges, and rounding) the same weight as bins with signal through the whitening, and did not return the correct shift —— on low-pass-filtered 32³ crops 0 / 10 of 10 shifts, and 0 / 10 at 64³ too. With periodic np.roll shifts it is exact, so a gate with only those could not see it. In 3 dimensions, compared with 2 dimensions (default Hann + 0.5), the window trims more of the overlap and whitening lifts noise bins more easily: measured hits (10 shifts × 3 seeds), low-pass crops of 32³ with shifts ±6: Hann + 0.5 gets 11 / 30, Hann + 0.25 19 / 30, Tukey + 0.25 28 / 30; periodic shifts give 9 / 30, 18 / 30 and 27 / 30 respectively (old 30 / 30). At 64³ and (1, 256, 256) all 3 hit every case (the old one got 0 on the low-pass crops). At an even lower band (cutoff 0.10), 32³ stays at 6 / 10 or below for all of them; this case remains hard. Hence the default is Tukey (α 0.5) + 0.25. The old behaviour is window=None, whitening=1.0 (the same numeric path as well).
• Circular correlation, so the part that runs off the edge wraps around from the opposite side. A shift beyond half the volume appears with flipped sign. With the windowed default, a small volume (32³) with a large periodic shift (2/10 of the side) can miss (the 27 / 30 above) —— if you know the shift is periodic, use window=None, whitening=1.0.
• An all-0 volume gives 0 and returns index 0.
• Integer precision. For sub-voxel accuracy go to refine_translation_lk / refine_peak_newton.
• Does not work when rotation or scale is present (order: match_logpolar_z → rotation correction → this op).
Raises ValueError: shapes differ, misspelled window, whitening outside [0, 1], device other than cpu in an environment without torch.
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• shape_desc_pose — py -3.11 examples_3d/shape_desc_pose.py
shift as input)match_pose)match_pca · moment_axes · match_logpolar_z
*Provenance: match3d.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.