Skip to content

Simulation and interoperability

solvephase is one part of an adaptive-optics simulation family and reuses its siblings rather than copying them:

Package Role here Install
aobasis Zernike, KL, Fourier and DM bases core dependency
pyturb atmospheric OPD (von Kármán phase screens) solvephase[interop]
getframes detector frames: shot/read/dark noise, gain, bias, saturation solvephase[interop]
makewfs wavefront-sensor images (shares solvephase's (y, x), metres and centred-pixel conventions) separately
HCIPy independent optics reference used by the validation suite solvephase[validation]

Built-in simulation

import solvephase as sp

pupil = sp.Pupil.circular(64, 8.0, obscuration=0.14)
model = sp.FocalPlaneModel(
    pupil, 1.6e-6, 64, sampling=2.0, diversity=sp.zernike_diversity(pupil, 4, [0.0, 0.4e-6])
)
truth = sp.random_aberration(pupil, 80e-9, n_modes=30, seed=1)
images = sp.simulate_images(model, truth, photons=1e6, background=5, read_noise=2, seed=2)

Turbulence from pyturb

from solvephase.interop import turbulence_opd

residual = turbulence_opd(pupil, r0=0.8, outer_scale=25.0, seed=3)  # OPD [m] over the pupil
print(f"{sp.rms(residual, pupil) * 1e9:.0f} nm RMS")

Detector frames from getframes

expose exposes normalized model images on a getframes camera preset (or your own getframes.Camera). It returns the raw ADU frames, frames converted back to photo-electrons, the read noise in electrons, and weights that mask saturated pixels. These are exactly what the Poisson likelihood needs.

from solvephase.interop import expose

frames = expose(
    sp.to_numpy(model.images(truth)), "andor_ikon_m934", photons=3e5, background=20, seed=4
)
result = sp.retrieve(
    frames.electrons,
    pupil,
    1.6e-6,
    sampling=2.0,
    diversity=[0.0, 0.4e-6],
    read_noise=frames.read_noise,
    weights=frames.weights,
)
print(
    frames.n_saturated,
    f"{sp.wavefront_error(result.opd, truth, pupil, remove='tiptilt') * 1e9:.1f} nm",
)

Saturation

Bright PSF cores easily exceed a detector's full well or ADC range. A saturated pixel left in the fit biases the wavefront by tens of percent. Always pass weights=frames.weights (or your own mask).

API

solvephase.interop.turbulence_opd

turbulence_opd(pupil, r0, *, outer_scale=25.0, r0_wavelength=5e-07, count=None, seed=None)

Atmospheric OPD over a pupil from :class:pyturb.PhaseScreen.

Parameters:

Name Type Description Default
pupil Pupil

The pupil (its pitch sets the screen pixel scale).

required
r0 float

Fried parameter in metres at r0_wavelength.

required
outer_scale float

Outer scale in metres (numpy.inf for Kolmogorov).

25.0
r0_wavelength float

Wavelength at which r0 is given.

5e-07
count int | None

Number of independent screens (None for a single map).

None
seed Any

Seed passed to pyturb.

None

Returns:

Type Description
``(ny, nx)`` (or ``(count, ny, nx)``) OPD in metres, zero outside the
pupil. OPD is achromatic, so the result is valid at any wavelength.

solvephase.interop.expose

expose(images, camera='andor_ikon_m934', *, exposure=1.0, photons=1000000.0, background=0.0, seed=None)

Expose normalized model images on a :class:getframes.Camera.

Parameters:

Name Type Description Default
images Any

(K, my, mx) normalized model images (each sums to about 1, as returned by :meth:FocalPlaneModel.images).

required
camera Any

A getframes preset name or a :class:getframes.Camera; its resolution is set to the image shape.

'andor_ikon_m934'
exposure float

Exposure time in seconds.

1.0
photons float | Any

Photons per channel arriving at the detector during the exposure.

1000000.0
background float

Background photons per pixel during the exposure.

0.0
seed int | None

Base seed; channel k uses seed + k.

None

solvephase.interop.DetectorImages dataclass

DetectorImages(adu, electrons, read_noise, truth, weights)

Detector frames and their conversion to photo-electrons.

Attributes:

Name Type Description
adu ndarray

(K, my, mx) raw frames in ADU.

electrons ndarray

(K, my, mx) bias-subtracted, gain-corrected frames in photo-electrons, for the Poisson likelihood.

read_noise float

Read noise in electrons (pass to read_noise= of the solver).

truth ndarray

(K, my, mx) noise-free expected photo-electrons.

weights ndarray

(K, my, mx) pixel weights for the solvers: 0 where the pixel is saturated (digitizer ceiling or full well), else 1.

n_saturated property

n_saturated

Number of saturated (masked) pixels.