tb_quat_color_rotate — 2D typed op

資料種類:qimageqimage

呼叫:fullseye.apply(img, "tb_quat_color_rotate", a=0.5, b=0.5)(2-D 的模型是一張圖 + 兩個純量旋鈕 a,b∈[0,1])

*無圖: 該運算子以 qimage 為輸入。從影像開始的 Studio 程式無法到達該型別,請參見下方的可執行範例。*

用法

繞某個 RGB 軸旋轉每個像素的顏色:`q x conj(q)`。→ (H, W, 4)。

> 以下的詳細說明為原文 —— 摘要與標題已翻譯。

The operation a complex pixel cannot express. `axis_rgb` is a direction in

RGB space and `angle_rad` the rotation about it; the rotor

`q = cos(a/2) + sin(a/2) * axis` is built with

`pose_quat.axis_angle_to_quat` and the conjugation is applied to the vector

part of every pixel, leaving the scalar part untouched (a conjugation cannot

move it).

Exactness and what it is worth

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

The conjugation is applied through the `3x3` matrix from

`pose_quat.quat_to_hom_mat3d` rather than by two per-pixel Hamilton

products, because for a `(512, 512)` image that is 500k quaternion

multiplications versus one `einsum`. The two are the *same map*, measured:

against per-pixel `pose_quat.quat_rotate_point_3d` the agreement is

`4.44e-16, the round trip rotate(rotate(q, ax, a), ax, -a)` returns

`q to 2.22e-16, and the colour magnitude is preserved to 2.22e-16`.

The matrix identity is also the honest limit of the *capability* claim.

`SO(3)` and the unit quaternions are isomorphic, so **a 3x3 orthogonal

colour matrix does exactly this and nothing is lost by using one** — measured

against an explicit `Rz(30 deg), the agreement is 2.22e-16`. What a

quaternion buys is 4 numbers instead of 9, exact closure under composition,

and `slerp`. Measured over 100,000 random small rotations composed in

sequence, the quaternion (renormalised each step, 4 divisions) drifts from

unit norm by 0.0 while the matrix (composed by multiplication, not

re-orthonormalised) drifts to `|R^T R - I| = 4.33e-14`.

**That advantage is real but it is nearly nothing, and an earlier revision of

this file oversold it by four orders of magnitude.** The same measurement

then read `4.4e-10` for the matrix, which looked like a decisive argument

for quaternions. It was not an argument about quaternions at all: it was the

`pose_quat` defect described below, feeding a slightly non-orthogonal

matrix into every one of the 100,000 steps. With that fixed the honest figure

is `4.33e-14`, i.e. ordinary rounding over 100k products. The lesson is the

repository's own: a number that flatters the thing you are building is the

one to re-measure first.

What a *channelwise* pipeline — three independent scalar filters, which is

what running the complex ops on R, G and B separately means — cannot do is

this operation at all: it never mixes channels, so it cannot turn red towards

green. Pure red rotated 90 degrees about the blue axis comes back as

`(-2.2e-16, 1.0, 0.0)`; no per-channel gain can put anything in the green

channel, because it starts at zero. That is the comparison in

`tests/test_quatimage.py`, and it is the one that is decisive.

Two traps in the rotor, both refused here regardless of the dependency

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

`pose_quat used to normalise as n / (norm + 1e-12)`. A zero axis

then returned `[cos(a/2), 0, 0, 0], which quat_to_hom_mat3d`

re-normalised to the identity: a rotation request silently became a no-op.

Worse, at `angle_rad = pi that same path produced [0, 0, 0, 0]`, whose

normalisation was `0/(0+1e-12) = 0` and whose matrix was again the identity

— a *180-degree* colour rotation silently becoming a copy. Both were reported

and have since been fixed in `pose_quat` itself (zero length now

raises; `axis_angle_to_quat(0, 0, 1, pi)` now returns exactly

`[0, 0, 0, 1]` with norm 1, where it used to return norm 0.9999999999990).

This operator nevertheless keeps both of its own guards —

:func:_require_direction on the axis, and an explicit unit-norm assertion

on the finished rotor (tolerance :data:_UNIT_TOL). A check that only holds

while a dependency behaves is not a check, and the caller of this module

should get this module's error message. A genuine pi rotation still works:

pure red about the blue axis gives `(-1.0, 1.2e-16, 0.0)`.

Raises `ValueError: *qimage* is not a valid (H, W, 4)` field;

*axis_rgb* is not a finite non-zero 3-vector; *angle_rad* is not a finite

real scalar; the constructed rotor is not unit norm.

Typed bridge of the quat op `quat_color_rotate into the 2-D evolution registry: the same implementation, called under the op(v, a, b) convention. This op has no tunable parameter; a and b` are unused.

參考(範例資料・文獻)

• 範例資料目錄(下載 URL / 授權) —— 2-D 用 skimage.data(BSD/公有領域)加合成圖,3-D 給出真實資料源(Stanford/PDS 等)的下載 URL。

• 運算子來歷與參考文獻 —— 該運算子族所依據的研究/方法出處。

可執行的範例(實際呼叫該運算子並已驗證的樣例)

• (尚無)

型別可銜接的下一個運算子(可接受 qimage 作為輸入)

identity · tb_quaternion_to_rgb · tb_quat_norm · tb_quat_conjugate_image · tb_quat_normalize_image · tb_monogenic_amplitude · tb_monogenic_phase · tb_monogenic_orientation

同類別(typed)

tb_points_to_voxel · tb_estimate_point_normals · tb_iss_keypoints · tb_angle_3points · tb_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal


*Provenance: ops.py — 2D 運算子登記表。本條目由 tools/opdocs.py md 自動產生(請勿手動編輯)。*

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