Skip to content

Optical Systems¤

TolimanOpticalSystem

dLuxToliman.optical_systems.TolimanOpticalSystem ¤

Bases: AngularOpticalSystem()

Source code in dLuxToliman/optical_systems.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
class TolimanOpticalSystem(AngularOpticalSystem()):
    def __init__(
        self,
        wf_npixels: int = 256,
        psf_npixels: int = 128,
        oversample: int = 4,
        psf_pixel_scale: float = 0.375,  # arcsec
        mask: Array = None,
        radial_orders: Array = None,
        noll_indices: Array = None,
        coefficients: Array = None,
        m1_diameter: float = 0.125,
        m2_diameter: float = 0.032,
        # n_struts: int = 3,
        strut_width: float = 0.002,
        z_oversize: float = 1.01,
        # strut_rotation: float = -np.pi / 2,
    ):
        """
        A pre-built dLux optics layer of the Toliman optical system. Note TolimanOptics uses units of arcseconds.

        Parameters
        ----------
        wf_npixels : int
            The pixel width the wavefront layer.
        psf_npixels : int
            The pixel width of the PSF.
        oversample : int
            The Nyquist oversampling factor of the PSF.
        psf_pixel_scale : float
            The pixel scale of the PSF in arcseconds per pixel.
        mask : Array
            The diffractive mask array to apply to the wavefront layer.
        radial_orders : Array = None
            The radial orders of the zernike polynomials to be used for the
            aberrations. Input of [0, 1] would give [Piston, Tilt X, Tilt Y],
            [1, 2] would be [Tilt X, Tilt Y, Defocus, Astig X, Astig Y], etc.
            The order must be increasing but does not have to be consecutive.
            If you want to specify specific zernikes across radial orders the
            noll_indices argument should be used instead.
        noll_indices : Array
            The zernike noll indices to be used for the aberrations. [1, 2, 3]
            would give [Piston, Tilt X, Tilt Y], [2, 3, 4] would be [Tilt X,
            Tilt Y, Defocus.
        coefficients : Array
            The coefficients of the Zernike polynomials.
        m1_diameter : float
            The outer diameter of the primary mirror in metres.
        m2_diameter : float
            The diameter of the secondary mirror in metres.
        strut_width : float
            The width of the struts in metres.
        z_oversize : float
            The oversize factor of the zernike polynomials relative to the
            primary mirror diameter. This is to ensure that the zernike
            polynomials are not truncated at the edge of the aperture, which
            can cause artefacts in the PSF. Default is 1.01.
        """

        # Diameter
        diameter = m1_diameter

        # Generate Aperture
        coords = dlu.pixel_coords(5 * wf_npixels, diameter)
        outer = dlu.circle(coords, m1_diameter / 2)
        inner = dlu.circle(coords, m2_diameter / 2, invert=True)
        spiders = dlu.spider(coords, strut_width, [0, 120, 240])
        transmission = dlu.combine([outer, inner, spiders], 5)

        # Hack this in for now, will be in dLux eventually
        if radial_orders is not None:
            radial_orders = np.array(radial_orders)

            if (radial_orders < 0).any():
                raise ValueError("Radial orders must be >= 0")

            noll_indices = []
            for order in radial_orders:
                start = dlu.triangular_number(order)
                stop = dlu.triangular_number(order + 1)
                noll_indices.append(np.arange(start, stop) + 1)
            noll_indices = np.concatenate(noll_indices).astype(int)

        if noll_indices is None:
            aperture = dll.TransmissiveLayer(transmission, normalise=True)
        else:
            # Generate Basis
            coords = dlu.pixel_coords(wf_npixels, diameter)
            basis = np.array(
                [dlu.zernike(i, coords, z_oversize * m1_diameter) for i in noll_indices]
            )

            if coefficients is None:
                coefficients = np.zeros(len(noll_indices))

            # Combine into BasisOptic class
            aperture = dll.BasisOptic(basis, transmission, coefficients, normalise=True)

        # # Generate Aperture
        # aperture = dLux.apertures.ApertureFactory(
        #     npixels=wf_npixels,
        #     radial_orders=radial_orders,
        #     noll_indices=noll_indices,
        #     coefficients=coefficients,
        #     secondary_ratio=m2_diameter / m1_diameter,
        #     nstruts=n_struts,
        #     strut_ratio=strut_width / m1_diameter,
        #     strut_rotation=strut_rotation,
        # )

        # Put this here for now, will be in dLux eventually
        def scale_array(array: Array, size_out: int, order: int) -> Array:
            xs = np.linspace(0, array.shape[0], size_out)
            xs, ys = np.meshgrid(xs, xs)
            return map_coordinates(array, np.array([ys, xs]), order=order)

        # Generate Mask
        if mask is None:
            path = os.path.join(os.path.dirname(__file__), "diffractive_pupil.npy")
            # arr_in = np.load(path)
            # ratio = wf_npixels / arr_in.shape[-1]
            mask = scale_array(np.load(path), wf_npixels, order=1)

            # Enforce full binary
            mask = mask.at[np.where(mask <= 0.5)].set(0.0)
            mask = mask.at[np.where(mask > 0.5)].set(1.0)

            # Enforce full binary
            mask = dlu.phase2opd(mask * np.pi, 585e-9)

            # Turn into optic
            mask = dll.AberratedLayer(mask)

        layers = [("aperture", aperture), ("pupil", mask)]

        super().__init__(
            wf_npixels=wf_npixels,
            diameter=diameter,
            layers=layers,
            # aperture=aperture,
            # mask=mask,
            psf_npixels=int(psf_npixels),
            oversample=int(oversample),
            # psf_pixel_scale = float(psf_pixel_scale)
            psf_pixel_scale=psf_pixel_scale,
        )

__init__(wf_npixels=256, psf_npixels=128, oversample=4, psf_pixel_scale=0.375, mask=None, radial_orders=None, noll_indices=None, coefficients=None, m1_diameter=0.125, m2_diameter=0.032, strut_width=0.002, z_oversize=1.01) ¤

A pre-built dLux optics layer of the Toliman optical system. Note TolimanOptics uses units of arcseconds.

Parameters:

Name Type Description Default
wf_npixels int

The pixel width the wavefront layer.

256
psf_npixels int

The pixel width of the PSF.

128
oversample int

The Nyquist oversampling factor of the PSF.

4
psf_pixel_scale float

The pixel scale of the PSF in arcseconds per pixel.

0.375
mask Array

The diffractive mask array to apply to the wavefront layer.

None
radial_orders Array = None

The radial orders of the zernike polynomials to be used for the aberrations. Input of [0, 1] would give [Piston, Tilt X, Tilt Y], [1, 2] would be [Tilt X, Tilt Y, Defocus, Astig X, Astig Y], etc. The order must be increasing but does not have to be consecutive. If you want to specify specific zernikes across radial orders the noll_indices argument should be used instead.

None
noll_indices Array

The zernike noll indices to be used for the aberrations. [1, 2, 3] would give [Piston, Tilt X, Tilt Y], [2, 3, 4] would be [Tilt X, Tilt Y, Defocus.

None
coefficients Array

The coefficients of the Zernike polynomials.

None
m1_diameter float

The outer diameter of the primary mirror in metres.

0.125
m2_diameter float

The diameter of the secondary mirror in metres.

0.032
strut_width float

The width of the struts in metres.

0.002
z_oversize float

The oversize factor of the zernike polynomials relative to the primary mirror diameter. This is to ensure that the zernike polynomials are not truncated at the edge of the aperture, which can cause artefacts in the PSF. Default is 1.01.

1.01
Source code in dLuxToliman/optical_systems.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def __init__(
    self,
    wf_npixels: int = 256,
    psf_npixels: int = 128,
    oversample: int = 4,
    psf_pixel_scale: float = 0.375,  # arcsec
    mask: Array = None,
    radial_orders: Array = None,
    noll_indices: Array = None,
    coefficients: Array = None,
    m1_diameter: float = 0.125,
    m2_diameter: float = 0.032,
    # n_struts: int = 3,
    strut_width: float = 0.002,
    z_oversize: float = 1.01,
    # strut_rotation: float = -np.pi / 2,
):
    """
    A pre-built dLux optics layer of the Toliman optical system. Note TolimanOptics uses units of arcseconds.

    Parameters
    ----------
    wf_npixels : int
        The pixel width the wavefront layer.
    psf_npixels : int
        The pixel width of the PSF.
    oversample : int
        The Nyquist oversampling factor of the PSF.
    psf_pixel_scale : float
        The pixel scale of the PSF in arcseconds per pixel.
    mask : Array
        The diffractive mask array to apply to the wavefront layer.
    radial_orders : Array = None
        The radial orders of the zernike polynomials to be used for the
        aberrations. Input of [0, 1] would give [Piston, Tilt X, Tilt Y],
        [1, 2] would be [Tilt X, Tilt Y, Defocus, Astig X, Astig Y], etc.
        The order must be increasing but does not have to be consecutive.
        If you want to specify specific zernikes across radial orders the
        noll_indices argument should be used instead.
    noll_indices : Array
        The zernike noll indices to be used for the aberrations. [1, 2, 3]
        would give [Piston, Tilt X, Tilt Y], [2, 3, 4] would be [Tilt X,
        Tilt Y, Defocus.
    coefficients : Array
        The coefficients of the Zernike polynomials.
    m1_diameter : float
        The outer diameter of the primary mirror in metres.
    m2_diameter : float
        The diameter of the secondary mirror in metres.
    strut_width : float
        The width of the struts in metres.
    z_oversize : float
        The oversize factor of the zernike polynomials relative to the
        primary mirror diameter. This is to ensure that the zernike
        polynomials are not truncated at the edge of the aperture, which
        can cause artefacts in the PSF. Default is 1.01.
    """

    # Diameter
    diameter = m1_diameter

    # Generate Aperture
    coords = dlu.pixel_coords(5 * wf_npixels, diameter)
    outer = dlu.circle(coords, m1_diameter / 2)
    inner = dlu.circle(coords, m2_diameter / 2, invert=True)
    spiders = dlu.spider(coords, strut_width, [0, 120, 240])
    transmission = dlu.combine([outer, inner, spiders], 5)

    # Hack this in for now, will be in dLux eventually
    if radial_orders is not None:
        radial_orders = np.array(radial_orders)

        if (radial_orders < 0).any():
            raise ValueError("Radial orders must be >= 0")

        noll_indices = []
        for order in radial_orders:
            start = dlu.triangular_number(order)
            stop = dlu.triangular_number(order + 1)
            noll_indices.append(np.arange(start, stop) + 1)
        noll_indices = np.concatenate(noll_indices).astype(int)

    if noll_indices is None:
        aperture = dll.TransmissiveLayer(transmission, normalise=True)
    else:
        # Generate Basis
        coords = dlu.pixel_coords(wf_npixels, diameter)
        basis = np.array(
            [dlu.zernike(i, coords, z_oversize * m1_diameter) for i in noll_indices]
        )

        if coefficients is None:
            coefficients = np.zeros(len(noll_indices))

        # Combine into BasisOptic class
        aperture = dll.BasisOptic(basis, transmission, coefficients, normalise=True)

    # # Generate Aperture
    # aperture = dLux.apertures.ApertureFactory(
    #     npixels=wf_npixels,
    #     radial_orders=radial_orders,
    #     noll_indices=noll_indices,
    #     coefficients=coefficients,
    #     secondary_ratio=m2_diameter / m1_diameter,
    #     nstruts=n_struts,
    #     strut_ratio=strut_width / m1_diameter,
    #     strut_rotation=strut_rotation,
    # )

    # Put this here for now, will be in dLux eventually
    def scale_array(array: Array, size_out: int, order: int) -> Array:
        xs = np.linspace(0, array.shape[0], size_out)
        xs, ys = np.meshgrid(xs, xs)
        return map_coordinates(array, np.array([ys, xs]), order=order)

    # Generate Mask
    if mask is None:
        path = os.path.join(os.path.dirname(__file__), "diffractive_pupil.npy")
        # arr_in = np.load(path)
        # ratio = wf_npixels / arr_in.shape[-1]
        mask = scale_array(np.load(path), wf_npixels, order=1)

        # Enforce full binary
        mask = mask.at[np.where(mask <= 0.5)].set(0.0)
        mask = mask.at[np.where(mask > 0.5)].set(1.0)

        # Enforce full binary
        mask = dlu.phase2opd(mask * np.pi, 585e-9)

        # Turn into optic
        mask = dll.AberratedLayer(mask)

    layers = [("aperture", aperture), ("pupil", mask)]

    super().__init__(
        wf_npixels=wf_npixels,
        diameter=diameter,
        layers=layers,
        # aperture=aperture,
        # mask=mask,
        psf_npixels=int(psf_npixels),
        oversample=int(oversample),
        # psf_pixel_scale = float(psf_pixel_scale)
        psf_pixel_scale=psf_pixel_scale,
    )