From 39b2b356afc383f42f7bc5b6c25a140bca7c6d9a Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Mon, 9 Dec 2024 12:49:57 -0700 Subject: [PATCH] Added `optika.sensors.AbstractImagingSensorMaterial.photons_incident()` method to compute the expected number of incident photons given the number of measured electrons. (#108) --- optika/sensors/_materials/_materials.py | 67 ++++++++++++++++++++ optika/sensors/_materials/_materials_test.py | 45 ++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/optika/sensors/_materials/_materials.py b/optika/sensors/_materials/_materials.py index 376e908..486c74e 100644 --- a/optika/sensors/_materials/_materials.py +++ b/optika/sensors/_materials/_materials.py @@ -775,6 +775,31 @@ def electrons_measured( The vector perpendicular to the surface of the sensor. """ + @abc.abstractmethod + def photons_incident( + self, + electrons: u.Quantity | na.AbstractScalar, + wavelength: u.Quantity | na.AbstractScalar, + direction: na.AbstractCartesian3dVectorArray, + normal: na.AbstractCartesian3dVectorArray, + ) -> na.AbstractScalar: + """ + Given the number of electrons measured by the sensor, + and a grid of wavelengths, compute the expected number of + photons incident on the sensor. + + Parameters + ---------- + electrons + The number of electrons measured by the sensor. + wavelength + An assumed grid of wavelengths for the incident photons. + direction + An assumed propagation direction for the incident photons. + normal + The vector perpendicular to the surface of the sensor. + """ + @abc.abstractmethod def charge_diffusion( self, @@ -822,6 +847,15 @@ def electrons_measured( return result + def photons_incident( + self, + electrons: u.Quantity | na.AbstractScalar, + wavelength: u.Quantity | na.AbstractScalar, + direction: na.AbstractCartesian3dVectorArray, + normal: na.AbstractCartesian3dVectorArray, + ) -> na.AbstractScalar: + return electrons * u.photon / u.electron + def charge_diffusion( self, rays: optika.rays.RayVectorArray, @@ -1121,6 +1155,39 @@ def electrons_measured( return result + def photons_incident( + self, + electrons: u.Quantity | na.AbstractScalar, + wavelength: u.Quantity | na.AbstractScalar, + direction: na.AbstractCartesian3dVectorArray, + normal: na.AbstractCartesian3dVectorArray, + ) -> na.AbstractScalar: + """ + Compute the expected number of incident photons for a given number + of electrons. + + Parameters + ---------- + electrons + The energy collected by the sensor in units of electrons. + wavelength + The assumed wavelength of the incident photons. + direction + The assumed direction of the incident photons. + normal + The vector perpendicular to the surface of the sensor. + """ + + qe = self.quantum_efficiency( + rays=optika.rays.RayVectorArray( + wavelength=wavelength, + direction=direction, + ), + normal=normal, + ) + + return electrons / qe + def charge_diffusion( self, rays: optika.rays.RayVectorArray, diff --git a/optika/sensors/_materials/_materials_test.py b/optika/sensors/_materials/_materials_test.py index 392a8bb..6066ab1 100644 --- a/optika/sensors/_materials/_materials_test.py +++ b/optika/sensors/_materials/_materials_test.py @@ -245,7 +245,7 @@ class AbstractTestAbstractImagingSensorMaterial( ) def test_electrons_measured( self, - a: optika.sensors.AbstractBackilluminatedCCDMaterial, + a: optika.sensors.AbstractImagingSensorMaterial, rays: optika.rays.RayVectorArray, normal: na.AbstractCartesian3dVectorArray, ): @@ -253,6 +253,47 @@ def test_electrons_measured( assert isinstance(result, optika.rays.RayVectorArray) assert np.all(result.intensity >= 0 * u.electron) + @pytest.mark.parametrize( + argnames="electrons", + argvalues=[ + 100 * u.electron, + ], + ) + @pytest.mark.parametrize( + argnames="wavelength", + argvalues=[ + 100 * u.AA, + ], + ) + @pytest.mark.parametrize( + argnames="direction", + argvalues=[ + na.Cartesian3dVectorArray(0, 0, 1), + ], + ) + @pytest.mark.parametrize( + argnames="normal", + argvalues=[ + na.Cartesian3dVectorArray(0, 0, -1), + ], + ) + def test_photons_incident( + self, + a: optika.sensors.AbstractImagingSensorMaterial, + electrons: u.Quantity | na.AbstractScalar, + wavelength: u.Quantity | na.AbstractScalar, + direction: na.AbstractCartesian3dVectorArray, + normal: na.AbstractCartesian3dVectorArray, + ): + result = a.photons_incident( + electrons=electrons, + wavelength=wavelength, + direction=direction, + normal=normal, + ) + assert isinstance(na.as_named_array(result), na.AbstractScalar) + assert result.unit.is_equivalent(u.photon) + @pytest.mark.parametrize( argnames="rays", argvalues=[ @@ -272,7 +313,7 @@ def test_electrons_measured( ) def test_charge_diffusion( self, - a: optika.sensors.AbstractBackilluminatedCCDMaterial, + a: optika.sensors.AbstractImagingSensorMaterial, rays: optika.rays.RayVectorArray, normal: na.AbstractCartesian3dVectorArray, ):