Skip to content

Commit

Permalink
All docstrings updated, tests passing
Browse files Browse the repository at this point in the history
  • Loading branch information
LouisDesdoigts committed Sep 21, 2023
1 parent 97368cb commit ae540da
Show file tree
Hide file tree
Showing 12 changed files with 596 additions and 519 deletions.
6 changes: 3 additions & 3 deletions dLux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@

from .optical_systems import (
BaseOpticalSystem as BaseOpticalSystem,
AngularOptics as AngularOptics,
CartesianOptics as CartesianOptics,
LayeredOptics as LayeredOptics,
AngularOpticalSystem as AngularOpticalSystem,
CartesianOpticalSystem as CartesianOpticalSystem,
LayeredOpticalSystem as LayeredOpticalSystem,
)

from .sources import (
Expand Down
8 changes: 5 additions & 3 deletions dLux/detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ class LayeredDetector(BaseDetector):

layers: OrderedDict

def __init__(self: LayeredDetector, layers: list):
def __init__(self: LayeredDetector, layers: list[DetectorLayer, tuple]):
"""
Parameters
----------
layers : list
A list of DetectorLayer objects to apply to the input psf.
layers : list[DetectorLayer, tuple]
A list of DetectorLayer objects to apply to the input psf. List entries
can be tuples of (key, layer) to specify a key, else the key is taken as
the class name of the layer.
"""
self.layers = dlu.list2dictionary(layers, True, DetectorLayer)
super().__init__()
Expand Down
142 changes: 71 additions & 71 deletions dLux/instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@ def model(self): # pragma: no cover

class Telescope(Instrument):
"""
A high level class designed to model the behaviour of a telescope. It
stores a series different ∂Lux objects, and primarily passes the relevant
information between these objects in order to coherently model some
telescope observation.
Class that represents a telescope instrument, holding an optical system, a source
object and (optionally) a detector object, automating the process of modelling
all three in conjunction.
To generate more complex instruments or a set of observations, the `Telescope`
class can be inherited and modified to suit the needs of the user.
Attributes
----------
optics : Optics
A Optics object that defines some optical configuration.
optics : OpticalSystem
An `OpticalSystem` object that defines the optical transformations of the
instrument.
source : Source
A dictionary of the various source objects that the instrument is
observing.
A `Source` or `Scene` to objects to model through the instrument.
detector : Detector
A Detector object that is used to model the various
instrumental effects on a psf.
A `Detector` object that defines the detector transformations of the
instrument.
"""

optics: OpticalSystem
Expand All @@ -50,16 +52,19 @@ def __init__(
detector: Detector = None,
):
"""
Constructor for the Telescope class.
Parameters
----------
optics : Optics
A pre-configured Optics object.
source : Union[list, Source]
Either a Scene, list of Sources, or an individual Source object.
optics : OpticalSystem
An `OpticalSystem` object that defines the optical transformations of the
instrument.
source : Source
A `Source` or `Scene` to objects to model through the instrument. Can be
either a single `Source` object, or a list of `Source` objects which is
then converted to a `Scene` object. The list entries can also be a tuple of
(key, source) in order to specify a key for the source in the scene.
detector : Detector = None
A pre-configured Detector object.
A `Detector` object that defines the detector transformations of the
instrument.
"""
# Optics
if not isinstance(optics, OpticalSystem):
Expand All @@ -85,18 +90,13 @@ def __init__(

def __getattr__(self: Telescope, key: str) -> object:
"""
Magic method designed to allow accessing of the various items within
the sub-dictionaries of this class via the 'class.attribute' method.
It is recommended that each dictionary key in the optical layers,
detector layers, and scene sources are unique to prevent unexpected
behaviour. In the case they there are identical keys across the
dictionaries This method prioritises searching for keys in the optical
layers, then detector layers, and then the scene sources.
Raises the attributes from the optics, source and detector to the top level of
the class.
Parameters
----------
key : str
The key of the item to be searched for in the sub-dictionaries.
The key of the item to be searched for.
Returns
-------
Expand All @@ -110,36 +110,26 @@ def __getattr__(self: Telescope, key: str) -> object:
f"{self.__class__.__name__} has no attribute " f"{key}."
)

def model(self: Telescope, return_psf: bool = False) -> Union[Array, dict]:
def model(self: Telescope, return_psf: bool = False) -> Array:
"""
A base level modelling function designed to robustly handle the
different combinations of inputs. Models the through the
instrument optics and detector.
Models the source objects through the optical system and detector.
Parameters
----------
return_psf : bool = False
Should the PSF object be returned instead of the psf Array?
Returns
-------
psf : Array, dict
The psf of the scene modelled through the optics with detector
and filter effects applied if they are supplied. Returns either as
a single array (if return_tree is false), or a dict of the output
for each source.
object : Array, PSF
if `return_psf` is False, the psf Array is returned.
If `return_psf` is True, the PSF object is returned.
"""
# Model optics: return_psf=True for more efficient source calculations
psfs = self.optics.model(self.source, return_psf=True)

# # Check for tree-like output from scene
# if not isinstance(psfs, PSF):
# # Define functions
# leaf_fn = lambda x: isinstance(x, PSF)
# get_psfs = lambda psf: psf.data.sum(tuple(range(psf.ndim)))
# get_pscales = lambda psf: psf.pixel_scale.mean()

# # Get values
# psf = dlu.map2array(get_psfs, psfs, leaf_fn).sum()
# pixel_scale = dlu.map2array(get_pscales, psfs, leaf_fn).mean()

# # Array based output
# else:
# Array based output
psf = psfs.data.sum(tuple(range(psfs.ndim)))
pixel_scale = psfs.pixel_scale.mean()

Expand All @@ -154,18 +144,26 @@ def model(self: Telescope, return_psf: bool = False) -> Union[Array, dict]:
return psf_obj.data


# TODO: Test and re-write the Dither class
class Dither(Telescope):
"""
Telescope class designed to apply a series of dithers to the instrument
and return the corresponding PSFs.
Simple extension of the `Telescope` class that applies a series of dithers to the
source positions before modelling the instrument. Serves both as a demonstration
of how to extend the `Telescope` class and as a useful tool for modelling
dithered observations.
Attributes
----------
dithers : Array, (radians)
optics : OpticalSystem
An `OpticalSystem` object that defines the optical transformations of the
instrument.
source : Source
A `Source` or `Scene` to objects to model through the instrument.
detector : Detector
A `Detector` object that defines the detector transformations of the
instrument.
dithers : Array, radians
The array of dithers to apply to the source positions. The shape of the
array should be (ndithers, 2) where ndithers is the number of dithers
and the second dimension is the (x, y) dither in radians.
array should be (ndithers, 2).
"""

dithers: Array
Expand All @@ -178,41 +176,43 @@ def __init__(
detector: Detector = None,
):
"""
Constructor for the Dither class.
Parameters
----------
dithers : Array, radians
The array of dithers to apply to the source positions. The shape of
the array should be (ndithers, 2) where ndithers is the number of
dithers and the second dimension is the (x, y) dither in radians.
optics : Optics
A pre-configured Optics object.
sourcs : Union[list, Source]
Either a list of sources or an individual Source object.
The array of dithers to apply to the source positions. The shape of the
array should be (ndithers, 2).
optics : OpticalSystem
An `OpticalSystem` object that defines the optical transformations of the
instrument.
source : Source
A `Source` or `Scene` to objects to model through the instrument. Can be
either a single `Source` object, or a list of `Source` objects which is
then converted to a `Scene` object. The list entries can also be a tuple of
(key, source) in order to specify a key for the source in the scene.
detector : Detector = None
A pre-configured Detector object.
A `Detector` object that defines the detector transformations of the
instrument.
"""
self.dithers = np.asarray(dithers, float)
if self.dithers.ndim != 2 or self.dithers.shape[1] != 2:
raise ValueError("dithers must be an array of shape (ndithers, 2)")
super().__init__(optics=optics, source=source, detector=detector)

def model(self: Telescope, return_psf=False) -> Array:
def model(self: Telescope, return_psf: bool = False) -> Array:
"""
Applies a series of dithers to the instrument sources and calls the
.model() method after applying each dither.
Models the source objects through the optical system and detector, while also
applying the dithers to the source positions.
Parameters
----------
instrument : Telescope
The array of dithers to apply to the source positions.
return_psf : bool = False
Should the PSF object be returned instead of the psf Array?
Returns
-------
psfs : Array
The psfs generated after applying the dithers to the source
positions.
object : Array, PSF
if `return_psf` is False, the psf Array is returned.
If `return_psf` is True, the PSF object is returned.
"""

def dither_and_model(dither, instrument):
Expand Down
2 changes: 1 addition & 1 deletion dLux/layers/aberrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self: Zernike, j: int):
raise ValueError("The Zernike index must be greater than 0.")
self.j = int(j)
self.name = dlu.zernike_name(j)
self.n, self.m = dlu.basis(j)
self.n, self.m = dlu.noll_indices(j)
self._c, self._k = dlu.zernike_factors(j)

def calculate(self: Zernike, coordinates: Array, nsides: int = 0) -> Array:
Expand Down
2 changes: 1 addition & 1 deletion dLux/layers/apertures.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ def eval_basis(self: ApertureLayer, coords: Array) -> Array:
eval_fn = lambda basis, coeff: dlu.eval_basis(basis, coeff)
return np.array(tree_map(eval_fn, basii, coeffs))

def transmission(
def transmissions(
self: ApertureLayer, coords: Array, pixel_scale: float
) -> Array:
"""
Expand Down
4 changes: 1 addition & 3 deletions dLux/layers/detector_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,7 @@ def __init__(self: DetectorLayer, value: float):
The value to add to the psf.
"""
super().__init__()
self.value = np.asarray(value, dtype=float)
if self.value.ndim != 0:
raise ValueError("value must be a scalar array.")
self.value = float(value)

def apply(self: DetectorLayer, psf: PSF) -> PSF:
"""
Expand Down
Loading

0 comments on commit ae540da

Please sign in to comment.