pupil_psf — OPTICS wave op

• Data kinds: image2d → image2d

• Call: import fullseye as fs; fs.ledger.pupil_psf(pupil, defocus_waves=0.0, wavelength_um=0.55, f_number=5.6, oversample=4, pixel_pitch_um=None, opd_waves=None) (implementation को सीधे call करना हो तो import optics; optics.pupil_psf(pupil, defocus_waves=0.0, wavelength_um=0.55, f_number=5.6, oversample=4, pixel_pitch_um=None, opd_waves=None), registry से लेना हो तो opsoptics.get("pupil_psf"))

उपयोग

defocus के साथ किसी भी pupil shape का diffraction PSF (योग 1)।

> नीचे का विस्तृत विवरण मूल text में है — सारांश और headings का अनुवाद हो चुका है।

*pupil* is a square (n, n) amplitude transmittance (0 = opaque, 1 = clear; a binary mask is the usual case) drawn on a grid whose full width is the pupil's clear diameter D — so a circle filling the grid is a conventional round stop, a W-shaped band or an off-axis hole inside the grid is just a different mask, and f_number = f / D refers to that full width in every case. The wavefront over the grid is W(rho) = defocus_waves * rho^2 (+ opd_waves) with rho the radius from the grid centre normalised to 1 at the grid half-width (the Seidel defocus W20; defocus_from_shift converts an axial shift to it), and the PSF is the Fraunhofer intensity of the pupil function

PSF = | FFT{ pupil * exp(i 2 pi W) } |^2

on a zero-padded M x M grid, M = n * oversample (rounded up to even), centred on sample M//2 and normalised to unit sum. The image plane sample spacing is

dx = lambda * N * n / M ~= lambda * N / oversample [um]

— with *pixel_pitch_um* the fine PSF is area-integrated onto detector pixels of that pitch (odd (K, K), centred on a pixel, unit sum), which is what an image convolution needs; the pitch must not be finer than dx. Without it the fine PSF is returned and dx is yours to compute from the formula (an image cannot carry it).

Returns a float64 image2d.

Ground truth it reproduces (measured, tests/test_optics.py):

• a circle filling a 64-sample grid, oversample = 16: the first dark ring at 1.2197 lambda N within 0.5 % of the Airy value (three wavelength / f-number pairs), and the same ring at the same *micrometre* radius within 5 % after binning to a pixel pitch of lambda N / 8 (2.1 % measured — the parabolic minimum on a 9.8-pixel ring, not the binning) — so the pitch bookkeeping is right in physical units, not only in samples; the binned spot is centro-symmetric to 1e-17 and correlates with airy_pattern sampled at the same pitch at 0.99999 (0.9999 at lambda N / 4, 0.9997 at lambda N / 3);

• pure defocus of a circular pupil: the on-axis intensity relative to the unaberrated peak is the closed-form [sin(pi W20)/(pi W20)]^2 (0.405 at half a wave, 0 at one wave — the dark centre of the one-wave defocused Airy spot), within 1 %;

• defocus_waves = 0 and a clear circular pupil is the Airy pattern of airy_pattern to the sampling of the disc edge;

• the sign identity: for a real pupil, -W is the complex conjugate of +W, so PSF(-W)(x) = PSF(+W)(-x) exactly. The test pins it on a W-shaped band: the two PSFs are mirror images through the centre to 1e-12, and they are *not* equal to each other (the W pupil is asymmetric, so the direction of defocus is visible in the blur), while for the circle they are equal (a symmetric pupil cannot tell the sign). Rotating the W pupil by 90/180/270 degrees rotates the PSF the same way (checked, so the asymmetry is the pupil's, not the grid's).

Raises ValueError: *pupil* is not 2-D, not square, smaller than 2x2, over the size cap, complex, masked or non-finite; negative transmittance; an all-opaque pupil (nothing to diffract, the normalisation would be 0/0); *opd_waves* not the same shape as *pupil*; non-finite *defocus_waves*; non-positive or non-finite *wavelength_um* / *f_number* / *pixel_pitch_um*; *oversample* outside [1, 64]; an FFT side over MAX_PUPIL_FFT; an aliased phase — more than MAX_WAVES_PER_SAMPLE waves between neighbouring pupil samples (the message says how many samples the grid needs); a pixel pitch finer than the fine sample spacing (raise *oversample*).

Scalar Fraunhofer optics: no polarisation, no high-NA obliquity, no pupil apodisation by the lens itself. The defocus term is the paraxial rho^2 (see defocus_from_shift). A pupil that reaches the grid edge is fine (the zero padding is the field stop); a pupil *larger* than the grid cannot be expressed — widen the grid and lower f_number.

पूरी family का input contract (fail-closed)

optics के सभी ops गणना से पहले input की जाँच करते हैं (कुछ भी चुपचाप पास नहीं होता):

• Unit argument के नाम में ही लिखी होती है — _mm / _um / _deg / _mrad। mm और µm की गड़बड़ी crash नहीं करती, बल्कि "सही दिखने वाला पर गलत उत्तर" देती है, इसलिए नाम से इसे रोका जाता है। मान के आकार से unit का अनुमान बिल्कुल नहीं लगाया जाता।

• String से ValueError — float('50') सफल हो जाता है, इसलिए बिना parse किया config value length के रूप में निकल जाता (मापा गया: thin_lens('50', '200') सही दिखने वाला 66.667 mm लौटा रहा था)। bool को भी True == 1 वाले implicit promotion के रूप में अस्वीकार किया जाता है।

• complex / masked array से ValueError (केवल real-valued slots; imaginary part को चुपचाप छोड़ना या mask हटाना अस्वीकार)। हर input पर NaN/Inf से ValueError।

• शून्य से भाग और उससे जुड़े मामलों को नाम लेकर अस्वीकार किया जाता है: focal length 0, radius of curvature 0, refractive index <= 0, पूरी तरह opaque aperture (सब 0, इसलिए normalisation 0/0), ऐसा PSF जिसका योग <= 0 हो, S0 = 0 वाला Stokes vector, और front focal point पर रखा object (image अनंत पर)।

• केवल 2 ops non-finite value लौटाते हैं, और वह भी contract के रूप में लिखा हुआ: depth_of_field hyperfocal distance से आगे far_mm = inf (hyperfocal distance की परिभाषा यही है) और gaussian_beam waist पर wavefront_radius_mm = inf (plane wavefront की radius of curvature)। दोनों साथ में एक finite साथी (far_is_infinite / curvature_per_mm) भी लौटाते हैं। इसके अलावा कोई भी चुपचाप आया NaN/Inf अंदर ही पकड़ा जाता है और ValueError देता है — "float64 overflow हुआ" और "उत्तर अनंत है" अलग-अलग दावे हैं, इसलिए पहले को दूसरे का चेहरा पहनाकर नहीं लौटाया जाता।

• Size की सीमा: generate होने वाले grid के लिए optics.MAX_GRID (4096), दिए गए field/PSF/aperture के लिए optics.MAX_FIELD_ELEMENTS (2^24), ABCD element chain के लिए optics.MAX_SYSTEM_ELEMENTS (1024), Zernike के लिए MAX_ZERNIKE_TERMS (512) / MAX_ZERNIKE_ORDER (40) / MAX_ZERNIKE_BASIS (2^25)। छोटे argument से बहुत बड़ा internal allocation होने वाले रास्तों (मापा गया: n_max=40 × 4096² पर 108 GB) को fail-closed ढंग से बंद किया गया है।

• भौतिक रूप से असंभव states भी अस्वीकार: degree of polarisation > 1 वाला Stokes vector, ऋणात्मक transmittance, ऋणात्मक intensity, और n-|m| odd जैसे अमान्य Zernike indices।

उपयोग की विस्तृत guide

• optics_imaging family guide

संदर्भ (sample data, साहित्य)

• Sample data catalog (DL URL / license) — 2-D के लिए skimage.data (BSD/public) + synthetic, 3-D के लिए real data sources (Stanford/PDS आदि) के DL URL।

• Operators का स्रोत और संदर्भ — इस op family के मूल research/methods के स्रोत।

• Algorithm का मानक स्रोत (लेखक, वर्ष) और उपयोग ऊपर की family usage guide में दिए गए हैं।

चलाए जा सकने वाले उदाहरण (verified samples जो सच में यह op call करते हैं)

• optics_imaging — py -3.11 examples/optics_imaging.py

वे अगले ops जिनसे type जुड़ता है (image2d को input के रूप में लेते हैं)

fraunhofer_pattern · pupil_blur · psf_to_mtf · edge_spread · veiling_glare_index · emva_pair_statistics · emva_defect_pixels · polarization_demosaic

इसी category के (wave)

airy_pattern · angular_spectrum_propagate · fraunhofer_pattern · fourier_plane_filter · four_f_filter · gaussian_beam · defocus_from_shift · pupil_blur


*Provenance: optics.py — OPTICS operator registry. यह per-op note tools/opdocs.py md से अपने-आप generate होता है (हाथ से edit न करें)।*

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