Computational radiometry#

Computational radiometry is used to model the propagation of radiant energy through an optical system. It uses geometry and known optical and imaging properties to compute the irradiance from an observed scene at a detector. Lentil’s radiometry submodule provides a few helpful objects and functions for working with ratiometric data.

End to end radiometric modeling is part science and part art and this user guide provides limited insight. For a more in-depth treatment of the subject see:

  • Electro-Optical System Analysis and Design: A Radiometry Perspective, Cornelius J. Willers

  • The Art of Radiometry, James M. Palmer

The Spectrum Object#

radiometry.Spectrum is a data structure for working with spectral data. A spectrum is defined by the following parameters:

  • wave - the wavelengths represented in the spectrum

  • value - the values corresponding to each wavelength

  • waveunit - the wavelength units

  • valueunit - the value units

Create a new Spectrum with

>>> import lentil
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> s = lentil.radiometry.Spectrum(wave=np.arange(400,650),
...                                value=np.ones(250),
...                                waveunit='nm', valueunit=None)
>>> plt.plot(s.wave, s.value)
>>> plt.grid()
>>> plt.xlabel(f'Wavelength [{s.waveunit}]')
>>> plt.ylabel('Transmission [A.U.]')
../../_images/radiometry-1.png

Creating Spectrum from CSV#

Spectrum objects can be created from CSV files using radiometry.Spectrum.from_csv(). Given a CSV file formatted as

Wavelength (nm), Flux
9.00452000e+01, 1.2757340e-17
9.01354000e+01, 1.7265205e-17
9.02258000e+01, 1.8341225e-17
...
2.98786752e+05, 1.3599320e-19
2.99086286e+05, 1.3535845e-19
2.99386120e+05, 1.3385094e-19

a Spectrum object is created with

>>> from lentil.radiometry import Spectrum
>>> import matplotlib.pyplot as plt
>>> vega = Spectrum.from_csv('vega.csv', waveunit='nm',
...                          valueunit='flam', header_rows=1)
>>> plt.plot(wave=vega.wave, value=vega.value)
>>> plt.grid()
>>> plt.xlabel('Wavelength [nm]')
>>> plt.ylabel('Flux [erg s^-1 cm^-2]')
../../_images/vega.png

Creating Spectrum from FITS#

Although there is no built-in function, it is also possible to create a Spectrum from a FITS file using Astropy. Here, we’ll create a dictionary of Johnson-Cousins filter transmissions as Spectrum objects and then plot their transmissions:

>>> from lentil.radiometry import Spectrum
>>> import matplotlib.pyplot as plt
>>> from astropy.io import fits
>>> jc = {}
>>> for f in ('U','B','V','R','I'):
...     hdul = fits.open(f'johnson_{f.lower()}.fits')
...     jc[f] = (Spectrum(wave=hdul[1].data['WAVELENGTH'],
...                       value=hdul[1].data['THROUGHPUT'],
...                       waveunit='nm', valueunit=None))
>>> for band in jc:
...     plt.plot(jc[band].wave, jc[band].value, label=band)
>>> plt.grid()
>>> plt.legend()
>>> plt.xlabel('Wavelength [nm]')
>>> plt.ylabel('Transmission [A.U.]')
../../_images/johnson.png

The exact layout of spectral data within a FITS file may vary, but this example illustrates a general approach for creating Spectrum objects from FITS data.

Units#

Spectrum objects provide support for a limited set of wavelength and flux units and allows for conversion between units.

The following wavelength units are supported:

waveunit

Unit

m, meter

SI base unit

um, micron

\(10^{-6}\ \mbox{m}\)

nm, nanometer

\(10^{-9}\ \mbox{m}\)

angstrom

\(10^{-10}\ \mbox{m}\)

The following flux units are supported:

valueunit

Units

photlam

\(\mbox{photons s}^{-1} \mbox{m}^{-2}\)

wlam

\(\mbox{W m}^{-2}\)

flam

\(\mbox{erg s}^{-1} \mbox{cm}^{-2}\)

Converting between units is done with the Spectrum’s to() method:

>>> import lentil
>>> import numpy as np
>>> s = lentil.radiometry.Spectrum(wave=np.arange(400,700,50),
...                                value=np.ones(6),
...                                waveunit='nm', valueunit=None)
>>> s.wave
array([400, 450, 500, 550, 600, 650])
>>> s.to('m')
>>> s.wave
array([4.0e-07, 4.5e-07, 5.0e-07, 5.5e-07, 6.0e-07, 6.5e-07])

Manipulating Spectrum objects#

Basic operations#

The following arithmetic operations are defined for Spectrum objects:

A new Spectrum object is created and returned for each operation:

>>> import lentil
>>> import numpy as np
>>> # multiply a spectrum by a scalar
>>> a = lentil.radiometry.Spectrum(wave=np.arange(400,700,50),
...                                value=np.ones(6),
...                                waveunit='nm', valueunit=None)
>>> b = a * 2
>>> b.value
array([2, 2, 2, 2, 2, 2])

>>> # add two spectrum together
>>> c = lentil.radiometry.Spectrum(wave=np.arange(400,700,50),
...                                value=2*np.ones(6),
...                                waveunit='nm', valueunit=None)
>>> d = a + c
>>> d.value
array([3, 3, 3, 3, 3, 3])

Arithmetic operations work on Spectrum objects in the following ways:

  • a scalar with a Spectrum elementwise over all Spectrum values

  • a vector with a Spectrum elementwise over all Spectrum values (note the vector length must match the size of the Spectrum)

  • a Spectrum with another Spectrum elementwise (note that all arithmetic operations are supported for fully and partially overlapping data and addition and subtraction are supported for disjoint data)

Standard arithmetic behavior is available using the appropriate overloaded operator (+, -, *, /, or **) with additional custom behavior defining wavelength sampling and value interpolation options available by calling the arithmetic method directly:

>>> import lentil
>>> import numpy as np
>>> a = lentil.radiometry.Spectrum(wave=np.arange(400,700,50),
...                                value=np.ones(6),
...                                waveunit='nm', valueunit=None)
>>> b = lentil.radiometry.Spectrum(wave=np.arange(500,700,25),
...                                value=2*np.ones(8),
...                                waveunit='nm', valueunit=None)
>>> c = a.multiply(b, sampling=100)
>>> c.value
array([3, 3, 3])

Cropping, trimming, and joining operations#

The following operations are available for manipulating Spectrum data:

Sampling and binning operations#

The following operations are available for adjusting the sampling of Spectrum data:

Blackbody Emitters#

Create a radiometry.Blackbody object with:

>>> import lentil
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> wave = np.arange(400,4000)
>>> temp = 5000
>>> src = lentil.radiometry.Blackbody(wave,temp,waveunit='nm')
>>> plt.plot(src.wave, src.value), plt.grid()
>>> plt.xlabel('Wavelength [nm]'), plt.ylabel('Flux [photons/sec/m^2/sr]')
../../_images/radiometry-21.png

Because Blackbody subclasses Spectrum, all of the Spectrum methods are available:

>>> src.to('wlam')
>>> plt.plot(src.wave, src.value), plt.grid()
>>> plt.xlabel('Wavelength [nm]'), plt.ylabel('Flux [W/m^2/sr]')
../../_images/radiometry-31.png

Path transmission and emission#

The radiometry.Material object is useful for representing an optic with a specific transmission (or reflectance), emission, and contamination level. Several materials can be combined together in a list to compute path transmission or emission using the radiometry.path_transmission() and radiometry.path_emission() functions: