riesz_displacement — QUAT motion op

Datenarten: videotable

Aufruf: import quatimage; quatimage.riesz_displacement(video, f_lo, f_hi, fps, scales: 'int' = 4) -> 'dict' (oder opsquat.get("riesz_displacement"))

Verwendung

Subpixel-Verschiebungsfeld aus der monogenen Phase. → dict.

> Die ausführliche Beschreibung unten ist der Originaltext — Zusammenfassung und Überschriften sind übersetzt.

The measuring sibling of :func:riesz_motion_magnify, and the direct

counterpart of `motionmag.phase_displacement`: nothing is amplified, the

displacement itself is returned in pixels.

Per radial band, the temporal phase deviation obeys

`dphi = -(kx*dx + ky*dy) where (kx, ky)` is the local wave vector —

here `k * n, with the *direction* n` read straight off the Riesz pair

(continuous, per pixel) and the *magnitude* `k` from the spectral

derivative `Im(conj(z) d_n z)/|z|^2`. Each band gives one linear

constraint on the same two unknowns and the bands are combined per pixel by

weighted least squares with weights `|z|^2`, solved by the closed-form 2x2

pseudo-inverse so that a rank-1 pixel (the aperture problem) returns the

component that *was* observed and exactly zero in the direction nothing

constrained.

Returns ``{"dx": (T, H, W), "dy": (T, H, W), "weight": (H, W),

"valid": (H, W) bool, "rank": (H, W) int8, "fps", "band_hz", "frames",

"wrap_limit_px", "reference_coherence"}`` — the same keys

`motionmag.phase_displacement` returns, so the two are drop-in comparable.

Head to head against the complex steerable route

------------------------------------------------

All of the following is measured against `motionmag.phase_displacement` on

identical clips (64x64x64, 32 fps, 4 Hz bin-centred, band 3-5 Hz), with the

truth from an exact Fourier phase ramp and the error read as the deviation of

the least-squares gain from 1. **The verdict is mixed and the losses are

stated first.**

*When the model holds — one moving component per band — the two are the same

answer.* A single grating, translated:

============ ====================== ======================

true d (px) Riesz relative error steerable rel. error

============ ====================== ======================

0.001 1.463e-13 1.694e-13

0.010 3.997e-15 6.217e-15

0.100 3.331e-16 0.0

0.500 3.331e-16 0.0

1.000 0.0 0.0

2.000 0.0 2.220e-16

3.000 0.0 0.0

3.050 2.220e-16 2.220e-16

3.060 1.332e-15 0.0

3.070 1.573e+00 <- broken 1.573e+00 <- broken

4.000 1.207e+00 <- broken 1.207e+00 <- broken

============ ====================== ======================

Both are exact to rounding, and **both break in the same place, between 3.06

and 3.07 px** — which is the closed-form `J0` zero, not an empirical

tolerance: the temporal-mean phase reference equals `c * J0(k*A)`, whose

first zero at `k*A = 2.4048 is A = 2.4048/(2*pi/8) = 3.0619` px for an

8 px grating. The Riesz route does not lift that ceiling, because the

ceiling belongs to the temporal-mean reference and not to the decomposition.

*Where the Riesz route loses, and it loses badly.* A radial band has no

orientation index, so two components at the same scale but different

orientations land in one band and the single-plane-wave model behind the

monogenic signal is simply false there. A steerable bank separates them by

filter. On `motionmag.synthesize_translation`, whose default is exactly

that situation:

================================== ================== ==================

clip Riesz rel. error steerable rel. err

================================== ================== ==================

lambda = (8, 16) px [the default] 1.299e-01 4.441e-16

lambda = (8, 32) px [2 octaves] 2.220e-16 0.0

lambda = (8, 8) px [same band] 6.256e-01 1.329e-02

================================== ================== ==================

A **13 % displacement error that does not shrink as the displacement shrinks,

with no exception and no NaN** — and 63 % when the two gratings share a

wavelength outright. Separate the components by two octaves and the error

returns to machine precision, which identifies the cause exactly. Any scene

with texture at several orientations in one octave — that is, most real

scenes — is in the bad case. This is the single most important limitation of

the Riesz route and no amount of tuning removes it.

*A second loss: it cannot measure everywhere.* The wave vector comes from the

Riesz pair, which vanishes at every even-symmetric point (local phase 0

or pi — the crest of a bright or dark line) even though the amplitude there

is at full strength. Measured on the single-grating clip, 1024 of 4096 pixels

(25.0 %) come back rank 0 against 0 of 4096 for the steerable route, whose

orientation comes from the filter and never degenerates. The affected pixels

are marked in `rank` and weighted zero, so they do not corrupt the answer —

but they are holes in the field.

*The theoretical win does not materialise.* Continuous per-pixel orientation

should beat a 4-orientation bank on oblique structure. Measured, it does not

— the raised-cosine angular windows already interpolate exactly:

=============== ================== ==================

grating (deg) Riesz rel. error steerable rel. err

=============== ================== ==================

0.0 3.331e-16 0.0

20.6 4.441e-16 4.441e-16

45.0 4.441e-16 4.441e-16

69.4 4.441e-16 4.441e-16

90.0 3.331e-16 0.0

=============== ================== ==================

*Two wins that are real.* Under noise the Riesz estimate is consistently

about twice as accurate, because it spends its degrees of freedom on 4 bands

instead of 19 and admits fewer noise-only sub-bands to the normal equations

(single grating, A = 0.5 px):

========== ================== ==================

sigma Riesz rel. error steerable rel. err

========== ================== ==================

0.001 1.812e-05 2.329e-05

0.010 3.008e-04 5.119e-04

0.050 4.047e-03 8.670e-03

========== ================== ==================

And it is cheaper: it builds `scales` = 4 sub-bands where the steerable

bank builds `scales * orientations + 3` = 19. Measured wall clock on the

64x64x64 clip, best of 7: 0.0888 s against 0.1063 s (1.20x) here, and

0.1034 s against 0.2163 s (2.09x) for the magnifiers — less than the

19:4 filter ratio suggests, because each Riesz band costs three inverse FFTs

(band, R1, R2) where a steerable band costs one.

Summary, honestly. Use the steerable route when the scene has structure

at several orientations per octave, which is the common case; use this one

when the scene is narrow-band, when the clip is noisy, or when the 2x is

worth having. The quaternion is the right *object* for the monogenic signal

and gives orientation for free; it does not make the measurement better.

Raises `ValueError`: *video* is not a valid clip or is over

:data:MAX_PYRAMID_ELEMENTS; the pass-band is empty, reaches DC or exceeds

Nyquist; *scales* is outside `[1, MAX_SCALES]`.

Ausführlicher Anwendungsleitfaden

Leitfaden zur Familie quaternion_monogenic

Referenzen (Beispieldaten, Literatur)

• Katalog der Beispieldaten (Download-URLs / Lizenzen) — 2-D nutzt skimage.data (BSD/Public Domain) plus synthetische Bilder, 3-D nennt Download-URLs echter Datenquellen (Stanford, PDS, …).

• Herkunft und Literatur der Operatoren — die Quellen der Forschung/Verfahren, auf denen diese Operatorfamilie beruht.

• Der kanonische Algorithmus (Autor, Jahr) und seine Anwendungen stehen im Familienleitfaden oben.

Ausführbare Beispiele (verifizierte Samples, die diesen Operator wirklich aufrufen)

quaternion_monogenicpy -3.11 examples/quaternion_monogenic.py

Typkompatible Folge-Operatoren (nehmen table als Eingabe)

Gleiche Kategorie (motion)

riesz_motion_magnify · riesz_displacement_series


*Provenance: quatimage.py — QUAT Operator-Registry. Diese Notiz wird von tools/opdocs.py md erzeugt (nicht von Hand bearbeiten).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.