fullseye

polarization_stokes_map — SPECULAR polarization op

使い方

Per-pixel linear Stokes images with DoLP and AoLP maps from an analyser sweep (4 DoFP angles or any N >= 3, least squares). → dict of (H, W) maps.

:func: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:

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 :func: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 :mod: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 :func: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 :func: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 :func: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 :func:polarization_stokes (no clamp in play).

S3 is not returned: linear analysers cannot measure it (see :func:polarization_stokes).

Raises ValueError: as :func: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.

詳しい使い方ガイド

参考(サンプルデータ・文献)

実行できる例(この op を実際に呼ぶ検証済みサンプル)

型が繋がる次の op(table を入力に取れる)

—

同カテゴリ(polarization)

polarization_render · polarization_separate · polarization_dolp_map · polarization_stokes


Provenance: specularity.py — SPECULAR operator registry. この per-op ノートは tools/opdocs.py md が自動生成(手編集しない)。

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