Skip to content

Pupils and bases

Pupils

A Pupil is a real, non-negative amplitude map on a square grid with its pixel pitch (m), reference diameter (m, which sets \(\lambda/D\) and the Zernike radius) and central obscuration ratio. Analytic constructors anti-alias edges by supersampling each pixel.

import numpy as np
import solvephase as sp

circ = sp.Pupil.circular(128, 8.0, obscuration=0.14, spiders=4, spider_width=0.05)
seg = sp.Pupil.segmented_hexagonal(128, rings=2, segment_size=1.3, gap=0.01)
presets = [sp.Pupil.keck(128), sp.Pupil.jwst(128), sp.Pupil.vlt(128), sp.Pupil.hst(128)]
custom = sp.Pupil.from_array(np.ones((32, 32)), diameter=1.0)
print(circ, seg.n_segments, [p.name for p in presets], custom.pitch)

Segmented pupils carry a label image, pupil.segments, with values 1..S. The presets are approximate geometries meant for algorithm work.

Bases

A Basis maps coefficients to an OPD over the pupil's illuminated pixels. Modes are unit RMS, so a coefficient is the RMS OPD of that mode in metres.

Constructor Modes Source
Basis.zernike(pupil, n, start=2) Zernike (Noll/ANSI/Fringe), annular when obscured aobasis
Basis.kl(pupil, n, r0=...) Karhunen-Loève of von Kármán turbulence, with prior variances aobasis
Basis.fourier(pupil, n) sines/cosines, orthonormalized aobasis
Basis.from_aobasis(pupil, Generator, n, ...) any aobasis generator aobasis
Basis.segments(pupil) piston/tip/tilt per segment built in
Basis.gaussian_dm(pupil, actuators_across) Gaussian influence functions (coefficients = DM commands) aobasis
Basis.influence_functions(pupil, ifs, m2c=...) your measured DM influence functions, optionally through an M2C built in
Basis.zonal(pupil) one value per pixel built in
Basis.from_maps(pupil, maps) any modes you supply built in

Bases combine with + (concatenate), select with subset, and orthonormalized() makes them orthonormal over the sampled pupil (spiders, gaps and grey edge pixels included), which improves solver conditioning.

pupil = sp.Pupil.circular(64, 1.0, obscuration=0.2)
basis = sp.Basis.zernike(pupil, 10) + sp.Basis.fourier(pupil, 6)
coeffs = np.zeros(basis.n_modes)
coeffs[2] = 50e-9  # 50 nm RMS of defocus (Z4)
opd = basis.synthesize(coeffs)
print(basis.labels[:4], np.allclose(basis.fit(opd), coeffs))

Retrieving in a DM basis gives commands you can apply directly. With the same influence functions, aobasis M2C matrices plug in through Basis.influence_functions(..., m2c=m2c), so results can feed a reconstructor or a controller such as pyRTC.