polarization op• 데이터 종류: polsweep → table
• 호출: import fullseye as fs; fs.ledger.polarization_stokes_map(images, angles_deg=(0.0, 45.0, 90.0, 135.0), max_violation_frac=0.0)(구현을 직접 호출하려면 import specularity; specularity.polarization_stokes_map(images, angles_deg=(0.0, 45.0, 90.0, 135.0), max_violation_frac=0.0), 원장에서 가져오려면 opsspecular.get("polarization_stokes_map"))
• GPU: 없음(CPU 참조 구현만)
검광자 스윕(DoFP 4 방위, 또는 임의의 N >= 3 방위를 최소제곱)으로부터 화소별 선형 Stokes 영상과 DoLP·AoLP 지도. → (H, W) 지도의 dict.
> 아래 상세 설명은 원문입니다 —— 요약과 제목은 번역되어 있습니다.
polarization_stokes answers for the whole field (a spatial mean); this is the same fit kept per pixel:
I(t) = 0.5 (S0 + S1 cos 2t + S2 sin 2t) solved for every pixel — in closed form for the four angles 0/45/90/135 of a division-of-focal-plane sensor (S0 = (I0 + I45 + I90 + I135)/2, S1 = I0 - I90, S2 = I45 - I135, the least-squares solution for that design), and by least squares for any other set of N >= 3 analyser angles.
Returns a dict:
• s0, s1, s2 — (H, W) linear Stokes images. Stack them with a zero S3 (np.stack([s0, s1, s2, 0 * s0], -1)) and they feed optics.mueller_apply's field form directly.
• dolp — sqrt(S1^2 + S2^2) / S0 in [0, 1].
• aolp_deg — 0.5 * atan2(S2, S1) in degrees, mapped into [0, 180) (an axis, so 0 and 180 are the same).
• lit — bool, S0 > 0 and above the noise-based dark floor (a0 > POLAR_DARK_K · σ_A, 2026-11-09 — see polarization_dolp_map); dolp is 0 and aolp_defined False where lit is False.
• aolp_defined — bool, S0 > 0 and hypot(S1, S2) > 1e-12 * S0.
• residual — RMS over the N frames of frame - fitted Malus curve, in the frames' units (added 2026-10-24). A sweep of a real scene fits exactly, so a large value says the frames do not agree with each other:
a misregistered or saturated channel. For the four DoFP angles it is
|I0 + I90 - I45 - I135| / 4; for three angles it is always 0 (the
fit is exact and nothing is left to check).
Where the ratios do not exist, the value is 0 and a mask says so — never NaN. At S0 = 0 DoLP is 0/0: it is returned as 0.0, the same
convention polarization_dolp_map states, with lit False. Where the linear part is zero (unpolarised light, or no light) AoLP has no
orientation: aolp_deg is 0.0 there and aolp_defined is False. "Zero" is relative — hypot(S1, S2) <= 1e-12 * S0 — because the fit leaves rounding dust: a uniform unpolarised sweep fits DoLP ~3e-16 at every scale from 1e-6 to 1e9 (measured), and with an exact-zero test 400/400 such pixels reported an "angle" that was pure rounding. DoLP itself is returned as fitted (that 3e-16 is an honest number). Use the masks before thresholding or averaging angles; a 0 in aolp_deg is not a measurement of 0 degrees.
Angles. *angles_deg* and the returned AoLP use the library convention:
from +x (along a row) toward +y (down the rows), clockwise on a displayed image (see _DEFAULT_ANGLES in this module and the polarisation section
of optics). A synthetic sweep made with optics.mueller_element("polarizer", t) of a beam at 21.5 degrees comes back as 21.5; the same frames labelled with negated angles come back as 158.5 — the test file pins both.
Physicality. The fit refuses sweeps whose fitted minimum is negative (as polarization_separate, controlled by *max_violation_frac*). When that clamp is allowed, a pixel can fit hypot(S1, S2) > S0; its (S1, S2) is then scaled back onto S0 (as polarization_stokes does for the mean) and a negative S0 is set to 0, so every returned pixel is a physical Stokes vector. With the default max_violation_frac=0 that only touches rounding-level excesses.
Ground truth (closed form, in tests/test_polarization_per_pixel_2026_10_23.py):
unpolarised light through an ideal polariser → DoLP 1 and AoLP = the polariser angle, per pixel, for a whole field of different angles; through optics.mueller_partial_polarizer → DoLP (ER - 1)/(ER + 1); Malus's law frames rebuilt from the returned S0, S1, S2 reproduce the input frames; the spatial mean of s0, s1, s2 equals polarization_stokes (no clamp in play).
S3 is not returned: linear analysers cannot measure it (see polarization_stokes).
Raises ValueError: as polarization_separate — *images* is not an (N, H, W) stack with N >= 3, exceeds the caps, or is non-finite; *angles_deg* does not match N or leaves the fit rank-deficient; more than *max_violation_frac* of the pixels fit a negative minimum. Also (2026-10-24): *images* has no pixels; *angles_deg* is ill-conditioned or looks like radians; a frame value below the negative floor counts as a violating pixel.
• specular_photometric 패밀리 가이드
• 샘플 데이터 카탈로그(DL URL / 라이선스) —— 2-D 는 skimage.data(BSD/public)+ 합성, 3-D 는 실데이터 소스(Stanford/PDS 등)의 DL URL.
• 연산자의 내력·참고문헌 —— 이 연산자 족의 바탕이 된 연구/기법의 출처.
• 알고리즘의 정전(저자·연도)과 용도는 위의 패밀리 사용 가이드에 적혀 있습니다.
• polarization_camera_pipeline — py -3.11 examples/polarization_camera_pipeline.py
table 를 입력으로 받는 것)—
polarization)polarization_render · polarization_separate · polarization_dolp_map · polarization_stokes
*Provenance: specularity.py — SPECULAR 연산자 레지스트리. 이 op 노트는 tools/opdocs.py md 가 자동 생성합니다(직접 편집하지 마세요).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.