typed op• 데이터 종류: qimage → qimage
• 호출: fullseye.apply(img, "tb_quat_color_rotate", a=0.5, b=0.5)(2-D 는 이미지 1 장 + 스칼라 노브 2 개 a,b∈[0,1] 모델)
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.
• 샘플 데이터 카탈로그(DL URL / 라이선스) —— 2-D 는 skimage.data(BSD/public)+ 합성, 3-D 는 실데이터 소스(Stanford/PDS 등)의 DL 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 연산자 레지스트리. 이 op 노트는 tools/opdocs.py md 가 자동 생성합니다(직접 편집하지 마세요).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.