Skip to content

v2.8.0

Choose a tag to compare

@roytsmart roytsmart released this 16 Sep 16:21
· 25 commits to main since this release
d0f36c8

The entrance pupil is resolved per field point (#222)

Normalized pupil coordinates now map onto the entrance pupil of each field
point rather than onto one box shared by the whole field. The corners of the
pupil are fit as a polynomial in field from the rays which graze both stops
along the edge of the field, plus one through the center of the field stop
which rides in the same solve. The fit is bounded by the shared box and falls
back to it wherever it collapses, inverts, or is singular, so this is never
worse than before.

This changes where a normalized pupil grid lands, and how many of its rays
survive.
On the ESIS design the shared box had 1.6x the area of any one
field point's pupil, so the unvignetted fraction of the default grid rises
from 0.19 to 0.32. Anything calibrated against a normalized pupil grid, such
as the sampling of a scene, will see different rays.

area_effective draws its samples in normalized coordinates and denormalizes
them afterwards, so every ray is mapped onto the entrance pupil of its own
field position and weighted by the area of its cell there. On FURST, whose
pupil is a sliver walking eight times its own width across one cell of the
default grid, this takes the effective area from 2.7x too high, because most
field points had no surviving ray and were dropped from the field average, to
within 0.05% of the converged value at the default grids. linearize keeps
its solve-once invariant.

Three frame defects were found on the way and are fixed:

  • The stop solver traced every trial ray from the launch surface's local
    depth, which broke any stop translated along the axis. FURST could not solve
    its stops at all; it does now, as written.
  • The stop axes were bound by position rather than by which stop they carried.
  • rayfunction un-transformed its result by the sensor's transformation alone
    rather than by the system's composed with it, so a system with a
    transformation reported rays in no frame at all. Every ray of the ESIS
    single-channel design now lands inside the sensor's aperture
    , where before
    all of them fell outside it; anything calibrated against that design's old
    sensor coordinates will shift.

A test applies one rigid motion to every surface of nine systems and checks
that nothing changes. optika.systems.StopSolveError, a ValueError, names
the failure of the stop solve. The stop-finding page of the docs describes the
strategy, the fit, and the frame each function takes and returns.

A sensor's absorbance can be interpolated over the angle of incidence (#231)

multilayer_efficiency and layer_absorbance take num_interpolation: given
an integer they solve the stack at that many angles spanning the range the
directions cover and interpolate each polarization state for the rest. The
multilayer materials pass theirs through, and
BackIlluminatedSiliconSensorMaterial gains the same field, since its
absorbance is the response of a stack of layers and cost as much as any other.
Nothing changes until a material asks for it.

On ESIS, whose rays meet the sensor at up to 16 degrees, 32 nodes reproduce the
exact absorbance to 9e-8, and a cold linearize at the default uncertainty
goes from 18.9 s to 12.9 s.

optika.zernikes and ZernikeSag (#172)

Noll-indexed, RMS-normalized Zernike polynomials and their analytic gradients,
following Noll (1976), and a sag which perturbs a base sag with a weighted sum
of them, for modeling measured or analytic figure error. The normal is
analytic and stays finite where the base surface is vertical; the intercept
seeds the generic secant solver from the base sag's own. Additive.

The generic sag intercept is solved in the sag's own frame (#230)

AbstractSag.intercept compared a depth in the parent frame with a sag
measured from the sag's own plane, so any transformation with a component
along the axis cancelled out and was silently ignored. A sphere translated
10 mm along z was intercepted as if it had not been. Sags with a closed-form
intercept were unaffected.

Plots choose the unit of the field (#226)

PolynomialVignettingModel.plot(), .plot_residual(), and
PolynomialDistortionModel.plot_residual() take the unit parameter the rest
of the library's plotting already takes, so a field of a tenth of a degree can
be drawn in arcseconds without refitting the model. Additive.

The docs build fits its limit again (#232)

The Replaceable.replace() example was rendered and executed on the page of
every one of the 91 classes which inherit it, which had the Read the Docs build
running at its 15 minute limit. It now lives on the class page alone, and the
build reads in 423 s where it read in 761 s.