typed op• 数据种类:qimage → qimage
• 调用:fullseye.apply(img, "tb_quat_color_rotate", a=0.5, b=0.5)(2-D 的模型是一张图 + 两个标量旋钮 a,b∈[0,1])

*图为在 128×128 合成输入上实际运行的输出。左为输入,右为输出。点云以俯视散点显示(亮度 = z),一维序列为折线,体数据为沿 z 的最大值投影,视频为中间帧,复数图像为幅值;无法成像的返回值直接显示数值。*
*旋钮 a 不改变输出(实测: 0.1 / 0.5 / 0.9 相同)。*
*旋钮 b 不改变输出(实测: 0.1 / 0.5 / 0.9 相同)。*
阶段(前置算子 → 本算子,从左到右):
▸ tb_quat_color_rotate: 处理阶段 (文档网站)
换别的图像(合成场景 / 照片 / 硬币。上排为输入,下排为对应输出。旋钮取默认):
▸ tb_quat_color_rotate: 其他输入图像 (文档网站)
绕某个 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).
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.
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 — _require_direction on the axis, and an explicit unit-norm assertion on the finished rotor (tolerance _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。
• 算子来历与参考文献 —— 该算子族所依据的研究/方法出处。
下面的程序已确认可以运行(与图相同的输入)。在 Studio 帮助中,此块会变成按钮,可当场加载并运行。
img_to_rgb 0.50 0.50 tb_rgb_to_quaternion 0.50 0.50 tb_quat_color_rotate 0.50 0.50
下面的示例调用的是底层账本算子 quat_color_rotate。此桥接算子只是把同一实现适配为 fn(v, a, b) 约定,行为完全相同(只是调用形式不同)。
• quaternion_monogenic — py -3.11 examples/quaternion_monogenic.py
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_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal · tb_voxel_grid_downsample
*Provenance: ops.py — 2D 算子登记表。本条目由 tools/opdocs.py md 自动生成(请勿手工编辑)。*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.