Skip to content

Optical Systems¤

BaseOpticalSystem

dLux.optical_systems.BaseOpticalSystem ¤

Bases: Base

Abstract base class for optical-system models.

Defines the required propagation and source-modelling interfaces used by concrete optical-system implementations.

UML

UML

Source code in dLux/optical_systems.py
 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
class BaseOpticalSystem(zdx.Base):
    """
    Abstract base class for optical-system models.

    Defines the required propagation and source-modelling interfaces used by
    concrete optical-system implementations.

    ??? abstract "UML"
        ![UML](../../assets/uml/BaseOpticalSystem.png)
    """

    def __init_subclass__(cls, **kwargs):
        """
        Automatically inherit method docstrings from parent class.
        """
        super().__init_subclass__(**kwargs)
        dlu.helpers.inherit_docstrings(cls, ["propagate_mono", "propagate", "model"])

    @abstractmethod
    def propagate_mono(
        self: BaseOpticalSystem,
        wavelength: float,
        offset: Array | None = None,
        return_wf: bool = False,
    ) -> Array | Wavefront:  # pragma: no cover
        """
        Propagates a monochromatic point source through the optical layers.

        Parameters
        ----------
        wavelength : float, metres
            The wavelength of the wavefront to propagate through the optical layers.
        offset : Array | None, radians = None
            The (x, y) offset from the optical axis of the source.
        return_wf: bool = False
            Should the Wavefront object be returned instead of the PSF array?

        Returns
        -------
        result : Array | Wavefront
            If `return_wf` is False, returns the PSF array.
            If `return_wf` is True, returns the Wavefront object.
        """

    @abstractmethod
    def propagate(
        self: BaseOpticalSystem,
        wavelengths: Array,
        offset: Array | None = None,
        weights: Array = None,
        return_wf: bool = False,
        return_psf: bool = False,
    ) -> Array | Wavefront | PSF:  # pragma: no cover
        """
        Propagates a Polychromatic point source through the optics.

        Parameters
        ----------
        wavelengths : Array, metres
            The wavelengths of the wavefronts to propagate through the optics.
        offset : Array | None, radians = None
            The (x, y) offset from the optical axis of the source.
        weights : Array = None
            The weight of each wavelength. If None, all weights are equal.
        return_wf : bool = False
            Should the Wavefront object be returned instead of the PSF array?
        return_psf : bool = False
            Should the PSF object be returned instead of the PSF array?

        Returns
        -------
        result : Array | Wavefront | PSF
            If `return_wf` is False and `return_psf` is False, returns the PSF array.
            If `return_wf` is True and `return_psf` is False, returns the Wavefront
                object.
            If `return_wf` is False and `return_psf` is True, returns the PSF object.

        """

    @abstractmethod
    def model(
        self: BaseOpticalSystem,
        source: Source,
        return_wf: bool = False,
        return_psf: bool = False,
    ) -> Array | Wavefront | PSF:  # pragma: no cover
        """
        Models the input Source object through the optics.

        Parameters
        ----------
        source : Source
            The Source object to model through the optics.
        return_wf : bool = False
            Should the Wavefront object be returned instead of the PSF array?
        return_psf : bool = False
            Should the PSF object be returned instead of the PSF array?

        Returns
        -------
        result : Array | Wavefront | PSF
            If `return_wf` is False and `return_psf` is False, returns the PSF array.
            If `return_wf` is True and `return_psf` is False, returns the Wavefront
                object.
            If `return_wf` is False and `return_psf` is True, returns the PSF object.
        """

__init_subclass__(**kwargs) ¤

Automatically inherit method docstrings from parent class.

Source code in dLux/optical_systems.py
41
42
43
44
45
46
def __init_subclass__(cls, **kwargs):
    """
    Automatically inherit method docstrings from parent class.
    """
    super().__init_subclass__(**kwargs)
    dlu.helpers.inherit_docstrings(cls, ["propagate_mono", "propagate", "model"])

model(source, return_wf=False, return_psf=False) abstractmethod ¤

Models the input Source object through the optics.

Parameters:

Name Type Description Default
source BaseSource

The Source object to model through the optics.

required
return_wf bool = False

Should the Wavefront object be returned instead of the PSF array?

False
return_psf bool = False

Should the PSF object be returned instead of the PSF array?

False

Returns:

Name Type Description
result Array | Wavefront | PSF

If return_wf is False and return_psf is False, returns the PSF array. If return_wf is True and return_psf is False, returns the Wavefront object. If return_wf is False and return_psf is True, returns the PSF object.

Source code in dLux/optical_systems.py
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
@abstractmethod
def model(
    self: BaseOpticalSystem,
    source: Source,
    return_wf: bool = False,
    return_psf: bool = False,
) -> Array | Wavefront | PSF:  # pragma: no cover
    """
    Models the input Source object through the optics.

    Parameters
    ----------
    source : Source
        The Source object to model through the optics.
    return_wf : bool = False
        Should the Wavefront object be returned instead of the PSF array?
    return_psf : bool = False
        Should the PSF object be returned instead of the PSF array?

    Returns
    -------
    result : Array | Wavefront | PSF
        If `return_wf` is False and `return_psf` is False, returns the PSF array.
        If `return_wf` is True and `return_psf` is False, returns the Wavefront
            object.
        If `return_wf` is False and `return_psf` is True, returns the PSF object.
    """

propagate(wavelengths, offset=None, weights=None, return_wf=False, return_psf=False) abstractmethod ¤

Propagates a Polychromatic point source through the optics.

Parameters:

Name Type Description Default
wavelengths (Array, metres)

The wavelengths of the wavefronts to propagate through the optics.

required
offset Array | None, radians = None

The (x, y) offset from the optical axis of the source.

None
weights Array = None

The weight of each wavelength. If None, all weights are equal.

None
return_wf bool = False

Should the Wavefront object be returned instead of the PSF array?

False
return_psf bool = False

Should the PSF object be returned instead of the PSF array?

False

Returns:

Name Type Description
result Array | Wavefront | PSF

If return_wf is False and return_psf is False, returns the PSF array. If return_wf is True and return_psf is False, returns the Wavefront object. If return_wf is False and return_psf is True, returns the PSF object.

Source code in dLux/optical_systems.py
 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
@abstractmethod
def propagate(
    self: BaseOpticalSystem,
    wavelengths: Array,
    offset: Array | None = None,
    weights: Array = None,
    return_wf: bool = False,
    return_psf: bool = False,
) -> Array | Wavefront | PSF:  # pragma: no cover
    """
    Propagates a Polychromatic point source through the optics.

    Parameters
    ----------
    wavelengths : Array, metres
        The wavelengths of the wavefronts to propagate through the optics.
    offset : Array | None, radians = None
        The (x, y) offset from the optical axis of the source.
    weights : Array = None
        The weight of each wavelength. If None, all weights are equal.
    return_wf : bool = False
        Should the Wavefront object be returned instead of the PSF array?
    return_psf : bool = False
        Should the PSF object be returned instead of the PSF array?

    Returns
    -------
    result : Array | Wavefront | PSF
        If `return_wf` is False and `return_psf` is False, returns the PSF array.
        If `return_wf` is True and `return_psf` is False, returns the Wavefront
            object.
        If `return_wf` is False and `return_psf` is True, returns the PSF object.

    """

propagate_mono(wavelength, offset=None, return_wf=False) abstractmethod ¤

Propagates a monochromatic point source through the optical layers.

Parameters:

Name Type Description Default
wavelength (float, metres)

The wavelength of the wavefront to propagate through the optical layers.

required
offset Array | None, radians = None

The (x, y) offset from the optical axis of the source.

None
return_wf bool

Should the Wavefront object be returned instead of the PSF array?

False

Returns:

Name Type Description
result Array | Wavefront

If return_wf is False, returns the PSF array. If return_wf is True, returns the Wavefront object.

Source code in dLux/optical_systems.py
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
@abstractmethod
def propagate_mono(
    self: BaseOpticalSystem,
    wavelength: float,
    offset: Array | None = None,
    return_wf: bool = False,
) -> Array | Wavefront:  # pragma: no cover
    """
    Propagates a monochromatic point source through the optical layers.

    Parameters
    ----------
    wavelength : float, metres
        The wavelength of the wavefront to propagate through the optical layers.
    offset : Array | None, radians = None
        The (x, y) offset from the optical axis of the source.
    return_wf: bool = False
        Should the Wavefront object be returned instead of the PSF array?

    Returns
    -------
    result : Array | Wavefront
        If `return_wf` is False, returns the PSF array.
        If `return_wf` is True, returns the Wavefront object.
    """
ParametricOpticalSystem

dLux.optical_systems.ParametricOpticalSystem ¤

Bases: OpticalSystem

Implements the attributes required for an optical system with a specific output pixel scale and number of pixels.

UML

UML

Attributes:

Name Type Description
psf_npixels int

The number of pixels of the final PSF.

oversample int

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

psf_pixel_scale float

The pixel scale of the final PSF.

Source code in dLux/optical_systems.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
class ParametricOpticalSystem(OpticalSystem):
    """
    Implements the attributes required for an optical system with a specific output
    pixel scale and number of pixels.

    ??? abstract "UML"
        ![UML](../../assets/uml/ParametricOpticalSystem.png)

    Attributes
    ----------
    psf_npixels : int
        The number of pixels of the final PSF.
    oversample : int
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    psf_pixel_scale : float
        The pixel scale of the final PSF.
    """

    psf_npixels: int
    oversample: int
    psf_pixel_scale: float

    def __init__(
        self: ParametricOpticalSystem,
        psf_npixels: int,
        psf_pixel_scale: float,
        oversample: int = 1,
        **kwargs,
    ):
        """
        Parameters
        ----------
        psf_npixels : int
            The number of pixels of the final PSF.
        psf_pixel_scale : float
            The pixel scale of the final PSF.
        oversample : int = 1.
            The oversampling factor of the final PSF. Decreases the psf_pixel_scale
            parameter while increasing the psf_npixels parameter.
        """
        self.psf_npixels = int(psf_npixels)
        self.oversample = int(oversample)
        self.psf_pixel_scale = float(psf_pixel_scale)
        super().__init__(**kwargs)

    @property
    def fov(self: ParametricOpticalSystem) -> float:
        """
        Field of view of the optical system in the units of the pixel scale.

        Returns
        -------
        fov : float
            Field of view, equal to ``psf_npixels * psf_pixel_scale``.
        """
        return self.psf_npixels * self.psf_pixel_scale

fov property ¤

Field of view of the optical system in the units of the pixel scale.

Returns:

Name Type Description
fov float

Field of view, equal to psf_npixels * psf_pixel_scale.

__init__(psf_npixels, psf_pixel_scale, oversample=1, **kwargs) ¤

Parameters:

Name Type Description Default
psf_npixels int

The number of pixels of the final PSF.

required
psf_pixel_scale float

The pixel scale of the final PSF.

required
oversample int = 1.

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

1
Source code in dLux/optical_systems.py
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
def __init__(
    self: ParametricOpticalSystem,
    psf_npixels: int,
    psf_pixel_scale: float,
    oversample: int = 1,
    **kwargs,
):
    """
    Parameters
    ----------
    psf_npixels : int
        The number of pixels of the final PSF.
    psf_pixel_scale : float
        The pixel scale of the final PSF.
    oversample : int = 1.
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    """
    self.psf_npixels = int(psf_npixels)
    self.oversample = int(oversample)
    self.psf_pixel_scale = float(psf_pixel_scale)
    super().__init__(**kwargs)
AngularOpticalSystem

dLux.optical_systems.AngularOpticalSystem ¤

Bases: ParametricLayeredOpticalSystem

An extension to the LayeredOpticalSystem class that propagates a wavefront to an image plane with psf_pixel_scale in units of arcseconds.

UML

UML

Attributes:

Name Type Description
wf_npixels int

The number of pixels representing the wavefront.

diameter (Array, metres)

The diameter of the initial wavefront to propagate.

layers OrderedDict

A series of OpticalLayer transformations to apply to wavefronts.

psf_npixels int

The number of pixels of the final PSF.

psf_pixel_scale (float, arcseconds)

The pixel scale of the final PSF.

oversample int

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

Source code in dLux/optical_systems.py
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
class AngularOpticalSystem(ParametricLayeredOpticalSystem):
    """
    An extension to the LayeredOpticalSystem class that propagates a wavefront to an
    image plane with `psf_pixel_scale` in units of arcseconds.

    ??? abstract "UML"
        ![UML](../../assets/uml/AngularOpticalSystem.png)

    Attributes
    ----------
    wf_npixels : int
        The number of pixels representing the wavefront.
    diameter : Array, metres
        The diameter of the initial wavefront to propagate.
    layers : OrderedDict
        A series of `OpticalLayer` transformations to apply to wavefronts.
    psf_npixels : int
        The number of pixels of the final PSF.
    psf_pixel_scale : float, arcseconds
        The pixel scale of the final PSF.
    oversample : int
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    """

    def __init__(
        self: AngularOpticalSystem,
        wf_npixels: int,
        diameter: float,
        layers: list[OpticalLayer | tuple[str, OpticalLayer]],
        psf_npixels: int,
        psf_pixel_scale: float,
        oversample: int = 1,
    ):
        """
        Parameters
        ----------
        wf_npixels : int
            The number of pixels representing the wavefront.
        diameter : Array, metres
            The diameter of the initial wavefront to propagate.
        layers : list[OpticalLayer | tuple[str, OpticalLayer]]
            A list of `OpticalLayer` transformations to apply to wavefronts. The list
            entries can be either `OpticalLayer` objects or tuples of (key, layer) to
            specify a key for the layer in the layers dictionary.
        psf_npixels : int
            The number of pixels of the final PSF.
        psf_pixel_scale : float, arcseconds
            The pixel scale of the final PSF in units of arcseconds.
        oversample : int
            The oversampling factor of the final PSF. Decreases the psf_pixel_scale
            parameter while increasing the psf_npixels parameter.
        """
        super().__init__(
            wf_npixels=wf_npixels,
            diameter=diameter,
            layers=layers,
            psf_npixels=psf_npixels,
            psf_pixel_scale=psf_pixel_scale,
            oversample=oversample,
        )

    def to_focus(
        self: AngularOpticalSystem,
        wavefront: Wavefront,
    ) -> Array | Wavefront:
        """
        Propagate the wavefront to the focal plane.

        Parameters
        ----------
        wavefront : Wavefront
            The wavefront to propagate to the focal plane.

        Returns
        -------
        result : Wavefront
            The propagated wavefront at the focal plane.
        """
        # Propagate
        true_pixel_scale = self.psf_pixel_scale / self.oversample
        pixel_scale = dlu.arcsec2rad(true_pixel_scale)
        psf_npixels = self.psf_npixels * self.oversample
        return wavefront.propagate(psf_npixels, pixel_scale)

__init__(wf_npixels, diameter, layers, psf_npixels, psf_pixel_scale, oversample=1) ¤

Parameters:

Name Type Description Default
wf_npixels int

The number of pixels representing the wavefront.

required
diameter (Array, metres)

The diameter of the initial wavefront to propagate.

required
layers list[OpticalLayer | tuple[str, OpticalLayer]]

A list of OpticalLayer transformations to apply to wavefronts. The list entries can be either OpticalLayer objects or tuples of (key, layer) to specify a key for the layer in the layers dictionary.

required
psf_npixels int

The number of pixels of the final PSF.

required
psf_pixel_scale (float, arcseconds)

The pixel scale of the final PSF in units of arcseconds.

required
oversample int

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

1
Source code in dLux/optical_systems.py
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
def __init__(
    self: AngularOpticalSystem,
    wf_npixels: int,
    diameter: float,
    layers: list[OpticalLayer | tuple[str, OpticalLayer]],
    psf_npixels: int,
    psf_pixel_scale: float,
    oversample: int = 1,
):
    """
    Parameters
    ----------
    wf_npixels : int
        The number of pixels representing the wavefront.
    diameter : Array, metres
        The diameter of the initial wavefront to propagate.
    layers : list[OpticalLayer | tuple[str, OpticalLayer]]
        A list of `OpticalLayer` transformations to apply to wavefronts. The list
        entries can be either `OpticalLayer` objects or tuples of (key, layer) to
        specify a key for the layer in the layers dictionary.
    psf_npixels : int
        The number of pixels of the final PSF.
    psf_pixel_scale : float, arcseconds
        The pixel scale of the final PSF in units of arcseconds.
    oversample : int
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    """
    super().__init__(
        wf_npixels=wf_npixels,
        diameter=diameter,
        layers=layers,
        psf_npixels=psf_npixels,
        psf_pixel_scale=psf_pixel_scale,
        oversample=oversample,
    )

to_focus(wavefront) ¤

Propagate the wavefront to the focal plane.

Parameters:

Name Type Description Default
wavefront Wavefront

The wavefront to propagate to the focal plane.

required

Returns:

Name Type Description
result Wavefront

The propagated wavefront at the focal plane.

Source code in dLux/optical_systems.py
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
def to_focus(
    self: AngularOpticalSystem,
    wavefront: Wavefront,
) -> Array | Wavefront:
    """
    Propagate the wavefront to the focal plane.

    Parameters
    ----------
    wavefront : Wavefront
        The wavefront to propagate to the focal plane.

    Returns
    -------
    result : Wavefront
        The propagated wavefront at the focal plane.
    """
    # Propagate
    true_pixel_scale = self.psf_pixel_scale / self.oversample
    pixel_scale = dlu.arcsec2rad(true_pixel_scale)
    psf_npixels = self.psf_npixels * self.oversample
    return wavefront.propagate(psf_npixels, pixel_scale)
CartesianOpticalSystem

dLux.optical_systems.CartesianOpticalSystem ¤

Bases: ParametricLayeredOpticalSystem

An extension to the LayeredOpticalSystem class that propagates a wavefront to an image plane with psf_pixel_scale in units of microns.

UML

UML

Attributes:

Name Type Description
wf_npixels int

The number of pixels representing the wavefront.

diameter (Array, metres)

The diameter of the initial wavefront to propagate.

layers OrderedDict

A series of OpticalLayer transformations to apply to wavefronts.

focal_length (float, metres)

The focal length of the system.

psf_npixels int

The number of pixels of the final PSF.

psf_pixel_scale (float, microns)

The pixel scale of the final PSF.

oversample int

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

Source code in dLux/optical_systems.py
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
class CartesianOpticalSystem(ParametricLayeredOpticalSystem):
    """
    An extension to the LayeredOpticalSystem class that propagates a wavefront to an
    image plane with `psf_pixel_scale` in units of microns.

    ??? abstract "UML"
        ![UML](../../assets/uml/CartesianOpticalSystem.png)

    Attributes
    ----------
    wf_npixels : int
        The number of pixels representing the wavefront.
    diameter : Array, metres
        The diameter of the initial wavefront to propagate.
    layers : OrderedDict
        A series of `OpticalLayer` transformations to apply to wavefronts.
    focal_length : float, metres
        The focal length of the system.
    psf_npixels : int
        The number of pixels of the final PSF.
    psf_pixel_scale : float, microns
        The pixel scale of the final PSF.
    oversample : int
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    """

    focal_length: float

    def __init__(
        self: CartesianOpticalSystem,
        wf_npixels: int,
        diameter: float,
        layers: list[OpticalLayer | tuple[str, OpticalLayer]],
        focal_length: float,
        psf_npixels: int,
        psf_pixel_scale: float,
        oversample: int = 1,
    ):
        """
        Parameters
        ----------
        wf_npixels : int
            The number of pixels representing the wavefront.
        diameter : Array, metres
            The diameter of the initial wavefront to propagate.
        layers : list[OpticalLayer | tuple[str, OpticalLayer]]
            A list of `OpticalLayer` transformations to apply to wavefronts. The list
            entries can be either `OpticalLayer` objects or tuples of (key, layer) to
            specify a key for the layer in the layers dictionary.
        focal_length : float, metres
            The focal length of the system.
        psf_npixels : int
            The number of pixels of the final PSF.
        psf_pixel_scale : float, microns
            The pixel scale of the final PSF in units of microns.
        oversample : int
            The oversampling factor of the final PSF. Decreases the psf_pixel_scale
            parameter while increasing the psf_npixels parameter.
        """
        self.focal_length = float(focal_length)

        super().__init__(
            wf_npixels=wf_npixels,
            diameter=diameter,
            layers=layers,
            psf_npixels=psf_npixels,
            psf_pixel_scale=psf_pixel_scale,
            oversample=oversample,
        )

    def to_focus(
        self: CartesianOpticalSystem,
        wavefront: Wavefront,
    ) -> Array | Wavefront:
        """
        Propagate the wavefront to the focal plane.

        Parameters
        ----------
        wavefront : Wavefront
            The wavefront to propagate to the focal plane.

        Returns
        -------
        result : Wavefront
            The propagated wavefront at the focal plane.
        """
        # Propagate
        true_pixel_scale = self.psf_pixel_scale / self.oversample
        pixel_scale = 1e-6 * true_pixel_scale
        psf_npixels = self.psf_npixels * self.oversample
        return wavefront.propagate(psf_npixels, pixel_scale)

__init__(wf_npixels, diameter, layers, focal_length, psf_npixels, psf_pixel_scale, oversample=1) ¤

Parameters:

Name Type Description Default
wf_npixels int

The number of pixels representing the wavefront.

required
diameter (Array, metres)

The diameter of the initial wavefront to propagate.

required
layers list[OpticalLayer | tuple[str, OpticalLayer]]

A list of OpticalLayer transformations to apply to wavefronts. The list entries can be either OpticalLayer objects or tuples of (key, layer) to specify a key for the layer in the layers dictionary.

required
focal_length (float, metres)

The focal length of the system.

required
psf_npixels int

The number of pixels of the final PSF.

required
psf_pixel_scale (float, microns)

The pixel scale of the final PSF in units of microns.

required
oversample int

The oversampling factor of the final PSF. Decreases the psf_pixel_scale parameter while increasing the psf_npixels parameter.

1
Source code in dLux/optical_systems.py
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
def __init__(
    self: CartesianOpticalSystem,
    wf_npixels: int,
    diameter: float,
    layers: list[OpticalLayer | tuple[str, OpticalLayer]],
    focal_length: float,
    psf_npixels: int,
    psf_pixel_scale: float,
    oversample: int = 1,
):
    """
    Parameters
    ----------
    wf_npixels : int
        The number of pixels representing the wavefront.
    diameter : Array, metres
        The diameter of the initial wavefront to propagate.
    layers : list[OpticalLayer | tuple[str, OpticalLayer]]
        A list of `OpticalLayer` transformations to apply to wavefronts. The list
        entries can be either `OpticalLayer` objects or tuples of (key, layer) to
        specify a key for the layer in the layers dictionary.
    focal_length : float, metres
        The focal length of the system.
    psf_npixels : int
        The number of pixels of the final PSF.
    psf_pixel_scale : float, microns
        The pixel scale of the final PSF in units of microns.
    oversample : int
        The oversampling factor of the final PSF. Decreases the psf_pixel_scale
        parameter while increasing the psf_npixels parameter.
    """
    self.focal_length = float(focal_length)

    super().__init__(
        wf_npixels=wf_npixels,
        diameter=diameter,
        layers=layers,
        psf_npixels=psf_npixels,
        psf_pixel_scale=psf_pixel_scale,
        oversample=oversample,
    )

to_focus(wavefront) ¤

Propagate the wavefront to the focal plane.

Parameters:

Name Type Description Default
wavefront Wavefront

The wavefront to propagate to the focal plane.

required

Returns:

Name Type Description
result Wavefront

The propagated wavefront at the focal plane.

Source code in dLux/optical_systems.py
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
def to_focus(
    self: CartesianOpticalSystem,
    wavefront: Wavefront,
) -> Array | Wavefront:
    """
    Propagate the wavefront to the focal plane.

    Parameters
    ----------
    wavefront : Wavefront
        The wavefront to propagate to the focal plane.

    Returns
    -------
    result : Wavefront
        The propagated wavefront at the focal plane.
    """
    # Propagate
    true_pixel_scale = self.psf_pixel_scale / self.oversample
    pixel_scale = 1e-6 * true_pixel_scale
    psf_npixels = self.psf_npixels * self.oversample
    return wavefront.propagate(psf_npixels, pixel_scale)
LayeredOpticalSystem

dLux.optical_systems.LayeredOpticalSystem ¤

Bases: OpticalSystem

A flexible optical system that allows for the arbitrary chaining of OpticalLayers.

UML

UML

Attributes:

Name Type Description
wf_npixels int

The size of the initial wavefront to propagate.

diameter (float, metres)

The diameter of the wavefront to propagate.

layers OrderedDict

A series of OpticalLayer transformations to apply to wavefronts.

Source code in dLux/optical_systems.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
class LayeredOpticalSystem(OpticalSystem):
    """
    A flexible optical system that allows for the arbitrary chaining of OpticalLayers.

    ??? abstract "UML"
        ![UML](../../assets/uml/LayeredOpticalSystem.png)

    Attributes
    ----------
    wf_npixels : int
        The size of the initial wavefront to propagate.
    diameter : float, metres
        The diameter of the wavefront to propagate.
    layers : OrderedDict
        A series of `OpticalLayer` transformations to apply to wavefronts.
    """

    wf_npixels: int
    diameter: float
    layers: OrderedDict

    def __init__(
        self: LayeredOpticalSystem,
        wf_npixels: int,
        diameter: float,
        layers: list[OpticalLayer | tuple[str, OpticalLayer]],
    ):
        """
        Parameters
        ----------
        wf_npixels : int
            The size of the initial wavefront to propagate.
        diameter : float
            The diameter of the wavefront to propagate.
        layers : list[OpticalLayer | tuple[str, OpticalLayer]]
            A list of `OpticalLayer` transformations to apply to wavefronts. The list
            entries can be either `OpticalLayer` objects or tuples of (key, layer) to
            specify a key for the layer in the layers dictionary.
        """
        self.wf_npixels = int(wf_npixels)
        self.diameter = float(diameter)
        self.layers = dlu.list2dictionary(layers, True, OpticalLayer)

    def __getattr__(self: LayeredOpticalSystem, key: str) -> Any:
        """
        Raises both the individual layers and the attributes of the layers via
        their keys.

        Parameters
        ----------
        key : str
            The key of the item to be searched for in the layers dictionary.

        Returns
        -------
        item : Any
            The item corresponding to the supplied key in the layers dictionary.
        """
        if key in self.layers.keys():
            return self.layers[key]
        for layer in list(self.layers.values()):
            if hasattr(layer, key):
                return getattr(layer, key)
        raise dlu.missing_attribute_error(self, key, list(self.layers.keys()))

    def initialise_wavefront(
        self: LayeredOpticalSystem, wavelength: Array, offset: Array = None
    ) -> Wavefront:
        """
        Initialises the wavefront for the propagate_mono method. and applies the offset
        as a tilt to the wavefront.

        Parameters
        ----------
        wavelength : Array
            The wavelength of the wavefront to propagate through the optical layers.
        offset : Array, radians = None
            The (x, y) offset from the optical axis of the source. Passed as angles in
            radians.

        Returns
        -------
        wavefront : Wavefront
            The initialised wavefront with the offset applied as a tilt.
        """
        if offset is None:
            offset = np.zeros(2)

        # Initialise wavefront
        wavefront = Wavefront(wavelength, self.wf_npixels, self.diameter)
        wavefront = wavefront.tilt(offset)
        return wavefront

    def propagate_mono(
        self: LayeredOpticalSystem,
        wavelength: Array,
        offset: Array | None = None,
        return_wf: bool = False,
    ) -> Array | Wavefront:
        """
        Propagates a monochromatic point source through the optical layers.

        Parameters
        ----------
        wavelength : float, metres
            The wavelength of the wavefront to propagate through the optical layers.
        offset : Array | None, radians = None
            The (x, y) offset from the optical axis of the source.
        return_wf : bool = False
            Should the Wavefront object be returned instead of the PSF array?

        Returns
        -------
        result : Array | Wavefront
            If `return_wf` is False, returns the PSF array.
            If `return_wf` is True, returns the Wavefront object.
        """
        # Initialise wavefront
        wavefront = self.initialise_wavefront(wavelength, offset)

        # Apply layers
        for layer in list(self.layers.values()):
            wavefront = layer(wavefront)

        # Return PSF or Wavefront
        if return_wf:
            return wavefront
        return wavefront.psf

    def debug_propagate_mono(
        self: LayeredOpticalSystem,
        wavelength: Array,
        offset: Array | None = None,
    ) -> Array | Wavefront:
        """
        Propagates a monochromatic wavefront through the layers, returning
        intermediate wavefront states for debugging.

        Parameters
        ----------
        wavelength : float, metres
            The wavelength of the wavefront to propagate.
        offset : Array | None, radians = None
            The (x, y) offset from the optical axis of the source.

        Returns
        -------
        wavefront : Wavefront
            The final propagated wavefront.
        outputs : dict
            Dictionary mapping layer names to their output wavefronts.
        """
        # Outputs dictionary
        outputs = {}

        # Initialise wavefront
        wavefront = self.initialise_wavefront(wavelength, offset)
        outputs["initial_wavefront"] = wavefront

        # Apply layers, storing the outputs
        for name, layer in self.layers.items():
            wavefront = layer(wavefront)
            outputs[name] = wavefront

        # Return the wavefront and the outputs dictionary for debugging
        return wavefront, outputs

    def insert_layer(
        self: LayeredOpticalSystem,
        layer: OpticalLayer | tuple[str, OpticalLayer],
        index: int,
    ) -> OpticalSystem:
        """
        Inserts a layer into the layers dictionary at a specified index. This function
        calls the list2dictionary function to ensure all keys remain unique. Note that
        this can result in some keys being modified if they are duplicates. The input
        'layer' can be a tuple of (key, layer) to specify a key, else the key is taken
        as the class name of the layer.

        Parameters
        ----------
        layer : OpticalLayer | tuple[str, OpticalLayer]
            The layer to be inserted.
        index : int
            The index at which to insert the layer.

        Returns
        -------
        optical_system : OpticalSystem
            The updated optical system.
        """
        return self.set(
            "layers", dlu.insert_layer(self.layers, layer, index, OpticalLayer)
        )

    def remove_layer(self: LayeredOpticalSystem, key: str) -> OpticalSystem:
        """
        Removes a layer from the layers dictionary, specified by its key.

        Parameters
        ----------
        key : str
            The key of the layer to be removed.

        Returns
        -------
        optical_system : OpticalSystem
            The updated optical system.
        """
        return self.set("layers", dlu.remove_layer(self.layers, key))

__getattr__(key) ¤

Raises both the individual layers and the attributes of the layers via their keys.

Parameters:

Name Type Description Default
key str

The key of the item to be searched for in the layers dictionary.

required

Returns:

Name Type Description
item Any

The item corresponding to the supplied key in the layers dictionary.

Source code in dLux/optical_systems.py
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
def __getattr__(self: LayeredOpticalSystem, key: str) -> Any:
    """
    Raises both the individual layers and the attributes of the layers via
    their keys.

    Parameters
    ----------
    key : str
        The key of the item to be searched for in the layers dictionary.

    Returns
    -------
    item : Any
        The item corresponding to the supplied key in the layers dictionary.
    """
    if key in self.layers.keys():
        return self.layers[key]
    for layer in list(self.layers.values()):
        if hasattr(layer, key):
            return getattr(layer, key)
    raise dlu.missing_attribute_error(self, key, list(self.layers.keys()))

__init__(wf_npixels, diameter, layers) ¤

Parameters:

Name Type Description Default
wf_npixels int

The size of the initial wavefront to propagate.

required
diameter float

The diameter of the wavefront to propagate.

required
layers list[OpticalLayer | tuple[str, OpticalLayer]]

A list of OpticalLayer transformations to apply to wavefronts. The list entries can be either OpticalLayer objects or tuples of (key, layer) to specify a key for the layer in the layers dictionary.

required
Source code in dLux/optical_systems.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def __init__(
    self: LayeredOpticalSystem,
    wf_npixels: int,
    diameter: float,
    layers: list[OpticalLayer | tuple[str, OpticalLayer]],
):
    """
    Parameters
    ----------
    wf_npixels : int
        The size of the initial wavefront to propagate.
    diameter : float
        The diameter of the wavefront to propagate.
    layers : list[OpticalLayer | tuple[str, OpticalLayer]]
        A list of `OpticalLayer` transformations to apply to wavefronts. The list
        entries can be either `OpticalLayer` objects or tuples of (key, layer) to
        specify a key for the layer in the layers dictionary.
    """
    self.wf_npixels = int(wf_npixels)
    self.diameter = float(diameter)
    self.layers = dlu.list2dictionary(layers, True, OpticalLayer)

debug_propagate_mono(wavelength, offset=None) ¤

Propagates a monochromatic wavefront through the layers, returning intermediate wavefront states for debugging.

Parameters:

Name Type Description Default
wavelength (float, metres)

The wavelength of the wavefront to propagate.

required
offset Array | None, radians = None

The (x, y) offset from the optical axis of the source.

None

Returns:

Name Type Description
wavefront Wavefront

The final propagated wavefront.

outputs dict

Dictionary mapping layer names to their output wavefronts.

Source code in dLux/optical_systems.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def debug_propagate_mono(
    self: LayeredOpticalSystem,
    wavelength: Array,
    offset: Array | None = None,
) -> Array | Wavefront:
    """
    Propagates a monochromatic wavefront through the layers, returning
    intermediate wavefront states for debugging.

    Parameters
    ----------
    wavelength : float, metres
        The wavelength of the wavefront to propagate.
    offset : Array | None, radians = None
        The (x, y) offset from the optical axis of the source.

    Returns
    -------
    wavefront : Wavefront
        The final propagated wavefront.
    outputs : dict
        Dictionary mapping layer names to their output wavefronts.
    """
    # Outputs dictionary
    outputs = {}

    # Initialise wavefront
    wavefront = self.initialise_wavefront(wavelength, offset)
    outputs["initial_wavefront"] = wavefront

    # Apply layers, storing the outputs
    for name, layer in self.layers.items():
        wavefront = layer(wavefront)
        outputs[name] = wavefront

    # Return the wavefront and the outputs dictionary for debugging
    return wavefront, outputs

initialise_wavefront(wavelength, offset=None) ¤

Initialises the wavefront for the propagate_mono method. and applies the offset as a tilt to the wavefront.

Parameters:

Name Type Description Default
wavelength Array

The wavelength of the wavefront to propagate through the optical layers.

required
offset Array, radians = None

The (x, y) offset from the optical axis of the source. Passed as angles in radians.

None

Returns:

Name Type Description
wavefront Wavefront

The initialised wavefront with the offset applied as a tilt.

Source code in dLux/optical_systems.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
def initialise_wavefront(
    self: LayeredOpticalSystem, wavelength: Array, offset: Array = None
) -> Wavefront:
    """
    Initialises the wavefront for the propagate_mono method. and applies the offset
    as a tilt to the wavefront.

    Parameters
    ----------
    wavelength : Array
        The wavelength of the wavefront to propagate through the optical layers.
    offset : Array, radians = None
        The (x, y) offset from the optical axis of the source. Passed as angles in
        radians.

    Returns
    -------
    wavefront : Wavefront
        The initialised wavefront with the offset applied as a tilt.
    """
    if offset is None:
        offset = np.zeros(2)

    # Initialise wavefront
    wavefront = Wavefront(wavelength, self.wf_npixels, self.diameter)
    wavefront = wavefront.tilt(offset)
    return wavefront

insert_layer(layer, index) ¤

Inserts a layer into the layers dictionary at a specified index. This function calls the list2dictionary function to ensure all keys remain unique. Note that this can result in some keys being modified if they are duplicates. The input 'layer' can be a tuple of (key, layer) to specify a key, else the key is taken as the class name of the layer.

Parameters:

Name Type Description Default
layer OpticalLayer | tuple[str, OpticalLayer]

The layer to be inserted.

required
index int

The index at which to insert the layer.

required

Returns:

Name Type Description
optical_system OpticalSystem

The updated optical system.

Source code in dLux/optical_systems.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
def insert_layer(
    self: LayeredOpticalSystem,
    layer: OpticalLayer | tuple[str, OpticalLayer],
    index: int,
) -> OpticalSystem:
    """
    Inserts a layer into the layers dictionary at a specified index. This function
    calls the list2dictionary function to ensure all keys remain unique. Note that
    this can result in some keys being modified if they are duplicates. The input
    'layer' can be a tuple of (key, layer) to specify a key, else the key is taken
    as the class name of the layer.

    Parameters
    ----------
    layer : OpticalLayer | tuple[str, OpticalLayer]
        The layer to be inserted.
    index : int
        The index at which to insert the layer.

    Returns
    -------
    optical_system : OpticalSystem
        The updated optical system.
    """
    return self.set(
        "layers", dlu.insert_layer(self.layers, layer, index, OpticalLayer)
    )

propagate_mono(wavelength, offset=None, return_wf=False) ¤

Propagates a monochromatic point source through the optical layers.

Parameters:

Name Type Description Default
wavelength (float, metres)

The wavelength of the wavefront to propagate through the optical layers.

required
offset Array | None, radians = None

The (x, y) offset from the optical axis of the source.

None
return_wf bool = False

Should the Wavefront object be returned instead of the PSF array?

False

Returns:

Name Type Description
result Array | Wavefront

If return_wf is False, returns the PSF array. If return_wf is True, returns the Wavefront object.

Source code in dLux/optical_systems.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
def propagate_mono(
    self: LayeredOpticalSystem,
    wavelength: Array,
    offset: Array | None = None,
    return_wf: bool = False,
) -> Array | Wavefront:
    """
    Propagates a monochromatic point source through the optical layers.

    Parameters
    ----------
    wavelength : float, metres
        The wavelength of the wavefront to propagate through the optical layers.
    offset : Array | None, radians = None
        The (x, y) offset from the optical axis of the source.
    return_wf : bool = False
        Should the Wavefront object be returned instead of the PSF array?

    Returns
    -------
    result : Array | Wavefront
        If `return_wf` is False, returns the PSF array.
        If `return_wf` is True, returns the Wavefront object.
    """
    # Initialise wavefront
    wavefront = self.initialise_wavefront(wavelength, offset)

    # Apply layers
    for layer in list(self.layers.values()):
        wavefront = layer(wavefront)

    # Return PSF or Wavefront
    if return_wf:
        return wavefront
    return wavefront.psf

remove_layer(key) ¤

Removes a layer from the layers dictionary, specified by its key.

Parameters:

Name Type Description Default
key str

The key of the layer to be removed.

required

Returns:

Name Type Description
optical_system OpticalSystem

The updated optical system.

Source code in dLux/optical_systems.py
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
def remove_layer(self: LayeredOpticalSystem, key: str) -> OpticalSystem:
    """
    Removes a layer from the layers dictionary, specified by its key.

    Parameters
    ----------
    key : str
        The key of the layer to be removed.

    Returns
    -------
    optical_system : OpticalSystem
        The updated optical system.
    """
    return self.set("layers", dlu.remove_layer(self.layers, key))