Extending Lentil#
Customizing Plane#
The Plane
class or any of the classes derived from Plane can be subclassed to modify
any of the default behavior. Reasons to do this may include but are not limited to:
Dynamically computing the
opd
attributeChanging the Plane - Wavefront interaction by redefining the
Plane.multiply()
methodModifying the way a Plane is resampled or rescaled
Some general guidance for how to safely subclass Plane is provided below.
Note
When subclassing Plane
make sure to call super().__init__()
, providing any
parameters defined in the subclass as necessary to ensure the Plane object is
correctly initialized.
Redefining the amplitude, OPD, or mask attributes#
Plane amplitude
, opd
, and
mask
are all defined as properties, but Python allows you to
redefine them as class attributes without issue:
import lentil
class CustomPlane(lentil.Plane):
def __init__(self):
amplitude = lentil.circle((256,256), 128)
opd = lentil.zernike(lentil.circlemask((256,256),128), 4)
super().__init__(amplitude=amplitude, opd=opd)
If more dynamic behavior is required, the property can be redefined. For example, to
return a new random OPD each time the opd
attribute is
accessed:
import numpy as np
import lentil
class CustomPlane(lentil.Plane):
def __init__(self):
mask = lentil.circlemask((256,256), 128)
amplitude = lentil.circle((256,256), 128)
super.__init__(mask=mask, amplitude=amplitude)
@property
def opd(self):
return lentil.zernike_compose(self.mask, np.random.random(10))
It is also straightforward to implement a custom opd
property to
provide a stateful OPD attribute:
import numpy as np
import lentil
class CustomPlane(lentil.Plane):
def __init__(self, x=np.zeros(10)):
mask = lentil.circlemask((256,256), 128)
amplitude = lentil.circle((256,256), 128)
super().__init__(mask=mask, amplitude=amplitude)
self.x = x
@property
def opd(self):
return lentil.zernike_compose(self.mask, self.x)
Note
Broadband diffraction propagations access the OPD, amplitude, and mask attributes for each propagatioon wavelength. Because these attributes remain fixed during a propagation, it is inefficient to repeatedly recompute them. To mitigate this, it can be very useful to provide a mechanism for freezing these dynamic attributes. There are many ways to do this. One approach is provided below:
import copy
import numpy as np
import lentil
class CustomPlane(lentil.Plane):
def __init__(self):
mask = lentil.circlemask((256,256), 128)
amplitude = lentil.circle((256,256), 128)
super().__init__(mask=mask, amplitude=amplitude)
@property
def opd(self):
return lentil.zernike_compose(self.mask, np.random.random(10))
def freeze(self):
# Return a copy of CustomPlane with the OPD attribute redefined
# to be a static copy of the OPD when freeze() is called
out = copy.deepcopy(self)
out.opd = self.opd.copy()
return out
Customizing Plane methods#
Any of the Plane
methods can be redefined in a subclass without restriction. Care
should be taken to ensure any redefined methods return data compatible with the
parent method’s return type to preserve compatibility within Lentil.