Source code for opticks.imaging_model.aperture

# opticks Models and analysis tools for optical system engineering
#
# Copyright (C) Egemen Imre
#
# Licensed under GNU GPL v3.0. See LICENSE.md for more info.

import numpy as np
from astropy.units import Quantity
from numpy import ndarray
from prysm.fttools import pad2d
from prysm.geometry import circle

from opticks import u
from opticks.utils.prysm_utils import Grid


[docs] class Aperture: grid: Grid """Grid object associated with the aperture""" def __init__(self, data: ndarray, grid: Grid) -> None: """Aperture class. Holds the Aperture data as defined by `prysm`. The aperture data is an ndarray mask of `bool` or 0 and 1 (or anything in between). Parameters ---------- data : ndarray aperture data grid : Grid Grid object associated with the aperture """ self.data = data self.grid = grid
[docs] @classmethod def circle_aperture( cls, aperture_diam: Quantity, samples, with_units=True, ) -> "Aperture": """Circle aperture model. This is valid for many if not most refractive optics. This is a thin wrapper around the aperture building procedure from `prysm`. The output is an Aperture object containing `ndarray` of bools, therefore the unit input is not important for the aperture generation. But the backing Grid object does use units. Parameters ---------- aperture_diam : Quantity aperture diameter in mm samples : int or tuple of int number of samples per dimension. If a scalar value, broadcast to both dimensions. Order is numpy axis convention, (row, col) with_units : bool, optional Grid returned with units (in mm and radians) Returns ------- aperture: Aperture Aperture mask object """ # aperture radius aperture_radius = aperture_diam / 2.0 # generate the square grid in polar coords grid = Grid.from_size(samples, aperture_diam) r, t = grid.polar() # generate aperture (circle mask applied to the square grid) aperture = circle(aperture_radius, r) # strip units if needed if not with_units: grid = grid.strip_units(u.mm) return Aperture(aperture, grid)
[docs] @classmethod def circle_aperture_with_obscuration( cls, aperture_diam: Quantity, obscuration_ratio: float, samples, with_units=True, ) -> "Aperture": """Circle aperture model with circular centre obscuration. This is valid for many reflective telescopes. The obscuration is usually the secondary mirror. This is a thin wrapper around the aperture building procedure from `prysm`. The output is an Aperture object containing `ndarray` of bools, therefore the unit input is not important for the aperture generation. Parameters ---------- aperture_diam : Quantity aperture diameter in mm obscuration_ratio: float obscuration ratio (between 0 and 1) samples : int or tuple of int number of samples per dimension. If a scalar value, broadcast to both dimensions. Order is numpy axis convention, (row, col) with_units : bool, optional Grid returned with units (in mm and radians) Returns ------- aperture: Aperture Aperture mask object """ # aperture radius aperture_radius = aperture_diam / 2.0 # generate the square grid in polar coords grid = Grid.from_size(samples, aperture_diam) r, t = grid.polar() # generate full aperture (circle mask applied to the square grid) full_aperture = circle(aperture_radius, r) # generate circular centre obscuration obscuration = circle(aperture_radius * obscuration_ratio, r) # apply full aperture minus obscuration as the mask aperture = full_aperture ^ obscuration # or full_aperture & ~obscuration # can also use floats of 0 and 1 instead of bool # this also enables varying the amount of light through the aperture # aperture = aperture.astype(float) # strip units if needed if not with_units: grid = grid.strip_units(u.mm) return Aperture(aperture, grid)
[docs] def scale_for_norm_sum_psf(self) -> "Aperture": """Generates a new, scaled Aperture that results in a Point Spread Function (PSF) that has a sum of 1.0. The aperture data (`data`) is divided by `sqrt(data.sum())`. """ ap_data = self.data # scale for PSF sum = 1.0 aperture_unity_sum = ap_data / np.sqrt(ap_data.sum()) return Aperture(aperture_unity_sum, self.grid)
[docs] def scale_for_norm_peak_psf(self, Q: float, Q_pad: float = 1) -> "Aperture": """Generates a new, scaled Aperture that results in a Point Spread Function (PSF) that has a peak of 1.0. `Q_pad` is used to pad the aperture if needed. It is passed on to the underlying `pad2d`. The aperture data (`data`) is multiplied by `Q x Q_pad x sqrt(data.size) / data.sum()`. Parameters ---------- Q : float Q factor used in scaling pupil samples to psf samples Q_pad : float, optional padding factor, by default 1 Returns ------- Aperture New Aperture object with scaled data """ ap_data = self.data # scale for PSF sum = 1.0 aperture_padded = pad2d(ap_data, Q=Q_pad) # type: ignore[arg-type] aperture_unity_peak = aperture_padded * ( Q_pad * Q * np.sqrt(ap_data.size) / ap_data.sum() ) return Aperture(aperture_unity_peak, self.grid)