OpticalComponents are elements that form the basis of the optics module. Each component transforms radiance data according to its Mueller matrix. An optical component requires an orientation defined by an skretrieval.OpticalGeometry, a radiance object (n x m numpy array of sasktran.StokesVector) and the the wavelengths of the radiances (numpy array of size n).

from skcomponents.optics import VerticalPolarizer
from skretrieval import OpticalGeometry
from sasktran import Geometry, StokesVector

# create a vertical polarizer
vert_pol = VerticalPolarizer()

# basis of the stokes vector.
stokes_basis = np.eye(3)

# create some test radiance data (this would normally be produced by a radiative transfer model)
model_wavel_nm = np.array([500])
radiance =  np.array([[StokesVector([1, 1, 0, 0], stokes_basis), StokesVector([1, -1, 0, 0], stokes_basis)]])

# define the geometry of the optical component
optical_geometry = OpticalGeometry(observer=np.array([1, 0, 0]),
                                   look_vector=np.array([0, 1, 0]),
                                   local_up=np.array([0, 0, 1]),

# pass radiance through the polarizer
radiance = vert_pol.model_radiance(optical_geometry, model_wavel_nm, radiance=radiance)

To create an optical system you can add components together. As an example, we can create a system than produces circularly polarized light:

from skcomponents.optics import VerticalPolarizer, QuarterWavePlate
optics = VerticalPolarizer() + QuarterWavePlate()
radiance = optics.model_radiance(optical_geometry, model_wavel_nm, radiance=radiance)


A SignalProcessor is a component that transforms signals after they have been converted from radiance to photons by a sensor. The purpose of a processor can be wide ranging, from simulating noise on a signal to inverting an interferogram.

from skcomponents.processing import ADC, PoissonCounting

# define a signal
signal = np.ones((3, 3)) * 1000

# create the processors
adc = ADC(bits=8, min_value=0, max_value=1000)
counter = PoissonCounting()

# pass signal through processors
signal = counter.process_signal(signal)
signal = adc.process_signal(signal)

Putting Everything Together

Optical components and signal processors are typically used in the context of a skretrieval.MeasurementSimulator. Using OpticalComponent, Sensor, and SignalProcessor can be incorporated in this framework by subclassing the desired :py:class`Sensor` and overriding the calculate_radiance() method as shown below:

from skretrieval.core import SpectralImager
from skcomponents.optics import VerticalPolarizer
from skcomponents.processing import ADC

class Instrument(SpectralImager):
    def model_radiance(self,
                       optical_geometry: OpticalGeometry,
                       model_wavel_nm: np.array,
                       model_geometry: Geometry,
                       radiance: np.array, wf=None) -> RadianceSpectralImage:

        # pass radiance through a vertical polarizer
        optics = VerticalPolarizer()
        if wf is None:
            radiance = optics.model_radiance(optical_geometry, model_wavel_nm, model_geometry, radiance)
            radiance, wf = optics.model_radiance(optical_geometry, model_wavel_nm, model_geometry, radiance, wf)

        # convert radiance to photons on each pixel
        model_value = super().model_radiance(optical_geometry, model_wavel_nm, model_geometry, radiance, wf=wf)

        # process the signal through electronic components
        adc = ADC(bits=12, min_value=0, max_value=5)
        model_value.data['radiance'].values = adc.process_signal(model_value.data['radiance'].values)
        return model_value