polarization op• Data kinds: none → matrix (an op determined by its arguments alone — it takes no image or data input)
• Call: import fullseye as fs; fs.ledger.mueller_element(kind='polarizer', angle_deg=0.0, retardance_deg=90.0) (to call the implementation directly, import optics; optics.mueller_element(kind='polarizer', angle_deg=0.0, retardance_deg=90.0); from the registry, opsoptics.get("mueller_element"))
• GPU: none (CPU reference implementation only)
A 4x4 real Mueller matrix for one polarisation element.
*kind* is one of MUELLER_KINDS — the same five as jones_element plus "depolarizer" (ideal, diag(1, 0, 0, 0)), which has no Jones counterpart at all. That extra kind is the reason this family exists: Jones algebra can only carry fully polarised light, Mueller algebra carries partial polarisation, scattering and depolarisation — which is what a real polarisation camera sees.
Angles are doubled inside (c = cos(2a), s = sin(2a)) because the Stokes parameters live on the Poincare sphere, where a physical rotation by a is a rotation by 2a.
Returns a (4, 4) float64 matrix acting on [S0, S1, S2, S3]; apply it
with mueller_apply and compose a train as M_last @ ... @ M_first.
Ground truth it reproduces exactly: an ideal polariser transmits exactly half of unpolarised light ([1,0,0,0] -> S0 = 0.5) and fully polarises it; two polarisers at relative angle theta transmit 0.5*cos^2(theta) (Malus); a rotator by 45 degrees turns horizontal into 45-degree linear; the depolariser leaves S0 and kills S1..S3. Cross-checked against the Jones family in the tests — for every kind and a sweep of angles, the Jones path and the Mueller path return the same Stokes vector to 1e-14, which is the only construction that catches a sign slip in either one.
Raises ValueError: an unknown *kind*; non-finite *angle_deg*; or, for kind="retarder", non-finite *retardance_deg*.
Ideal, lossless (except the polariser's physical loss), normal-incidence elements; no diattenuation-plus-retardance combinations, no depolarisation other than the ideal case. A polariser with a finite extinction ratio
is mueller_partial_polarizer.
Per-pixel fields (2026-10-23). An array *angle_deg* (or, for kind="retarder", an array *retardance_deg*) returns (*shape, 4, 4), one matrix per entry of the broadcast shape — an isoclinic angle map and a retardance map of a photoelastic part give the whole field in one call, ready for mueller_apply's field form. Before this the call raised ValueError and the photoelasticity PoC had to assemble (H, W, 4, 4) by hand. Scalars take the original code path untouched (scalar results are bit-for-bit what they were; pinned by a test against a verbatim copy of the old code). Output capped at MAX_FIELD_ELEMENTS numbers.
Angle convention. *angle_deg* is measured from +x (along a row) toward +y (down the rows): clockwise on a displayed image — the sense of atan2(ny, nx) of a normal map and of 0.5 * atan2(2 txy, sxx - syy) for image-coordinate stresses. See the polarisation section comment of this module.
Every optics op validates its input before computing (nothing slips through silently):
• Units are baked into the argument name — _mm / _um / _deg / _mrad. Confusing mm with µm does not crash; it yields a plausible-looking wrong answer, so the name prevents it. Nothing here guesses the unit from the magnitude.
• Strings raise ValueError — float('50') succeeds, so an unparsed configuration value would slip through as a length (measured: thin_lens('50', '200') returned a plausible 66.667 mm). bool is refused too, as the implicit promotion True == 1.
• complex / masked arrays raise ValueError (real-valued slots only; silently dropping the imaginary part or peeling off the mask is refused). NaN/Inf raises ValueError on every input. Exception, declared: the u_map of bottle_fit_from_pattern takes pixels that could not be measured as NaN (infinity is still refused).
• Division by zero and its relatives are refused by name: focal length 0, radius of curvature 0, refractive index <= 0, a fully opaque aperture (all zeros, so the normalisation is 0/0), a PSF whose sum is <= 0, a Stokes vector with S0 = 0, and an object sitting at the front focal point (the image is at infinity).
• Only two ops return a non-finite value, and both state it as a contract: depth_of_field returns far_mm = inf beyond the hyperfocal distance (that is what the hyperfocal distance means), and gaussian_beam returns wavefront_radius_mm = inf at the waist (the radius of curvature of a plane wavefront). Both also return a finite companion (far_is_infinite / curvature_per_mm). Any other silent NaN/Inf is detected internally and raises ValueError — "float64 overflowed" and "the answer is infinite" are different claims, so the first is never returned wearing the face of the second. Exception, declared: bottle_background_map returns NaN in u_mm for pixels whose ray is unusable (total internal reflection and the like), together with a valid mask and the counts.
• Size caps: generated grids are capped by optics.MAX_GRID (4096); supplied fields/PSFs/apertures by optics.MAX_FIELD_ELEMENTS (2^24); ABCD element chains by optics.MAX_SYSTEM_ELEMENTS (1024); Zernike by MAX_ZERNIKE_TERMS (512) / MAX_ZERNIKE_ORDER (40) / MAX_ZERNIKE_BASIS (2^25). This closes, fail-closed, the paths where a small argument triggers a huge internal allocation (measured: n_max=40 × 4096² needs 108 GB).
• Physically impossible states are refused too: a Stokes vector with degree of polarisation > 1, negative transmittance, negative intensity, and invalid Zernike indices such as n-|m| odd.
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• The canonical algorithm (author, year) and its uses are named in the family usage guide above.
• optics_imaging — py -3.11 examples/optics_imaging.py
• poc_mueller_dofp_observability — py -3.11 examples/poc_mueller_dofp_observability.py
• poc_photoelasticity — py -3.11 examples/poc_photoelasticity.py
• polarization_camera_pipeline — py -3.11 examples/polarization_camera_pipeline.py
matrix as input)abcd_trace · emva_filter_distortion · mueller_apply · mueller_checks · equirect_rotate · pixel_shift_capture · pixel_shift_merge · pixel_shift_motion_mask
polarization)jones_element · jones_apply · stokes_from_jones · mueller_partial_polarizer · mueller_apply · stokes_analyze · polarization_demosaic · polarization_demosaic_color
*Provenance: optics.py — OPTICS operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.