Skip to content

Commit

Permalink
Added optika.sensors.E2VCCD97Material class. (#6)
Browse files Browse the repository at this point in the history
Added `optika.sensors.E2VCCD97Material` class.
  • Loading branch information
byrdie authored Jan 15, 2024
1 parent 50d617d commit 3807622
Show file tree
Hide file tree
Showing 9 changed files with 454 additions and 3 deletions.
8 changes: 8 additions & 0 deletions docs/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ @article{Stern1994
doi = {10.1364/AO.33.002521},
abstract = {We report quantum efficiency measurements of backilluminated, ion-implanted, laser-annealed CCD's in the wavelength range 13--10,000 {\AA}. The equivalent quantum efficiency (the equivalent photons detected per incident photon) ranges from a minimum of 5\% at 1216 {\AA} to a maximum of 87\% at 135 {\AA}. Using a simple relationship for the charge-collection efficiency of the CCD pixels as a function of depth, we present a semiempirical model with few parameters that reproduces our measurements with a fair degree of accuracy. The advantage of this model is that it can be used to predict CCD quantum efficiency performance for shallow backside implanted devices without a detailed solution of a system of differential equations, as in conventional approaches, and it yields a simple analytic form for the charge-collection efficiency that is adequate for detector calibration purposes. Making detailed assumptions about the dopant profile, we also solve the current density and continuity equations in order to relate our semiempirical model parameters to surface and bulk device properties. The latter procedure helps to better establish device processing parameters for a given level of CCD quantum efficiency performance.},
}
@techreport{Moody2017,
author = {Ian Moody and Marc Watkins and Ray Bell and Matthew Soman and Jonathan Keelan and Andrew Holland},
title = {CCD QE in the Soft X-ray Range},
institution = {e2v},
month={March},
year={2017},
url={https://oro.open.ac.uk/49003/1/Moody%202017%20-%20SXRQE_WhitePaper.pdf},
}
1 change: 1 addition & 0 deletions optika/sensors/_materials/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from ._materials import *
from ._e2v_ccd97 import *
1 change: 1 addition & 0 deletions optika/sensors/_materials/_e2v_ccd97/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._e2v_ccd97 import *
184 changes: 184 additions & 0 deletions optika/sensors/_materials/_e2v_ccd97/_e2v_ccd97.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import functools
import pathlib
import numpy as np
import scipy.optimize
import astropy.units as u
import named_arrays as na
from .._materials import quantum_efficiency_effective
from .._materials import AbstractBackilluminatedCCDMaterial

__all__ = [
"E2VCCD97Material",
]


class E2VCCD97Material(
AbstractBackilluminatedCCDMaterial,
):
"""
A model of the light-sensitive material of an e2v CCD90 sensor based on
measurements by :cite:t:`Moody2017`.
Examples
--------
Plot the measured E2VCCD97 quantum efficiency vs the fitted
quantum efficiency calculated using the method of :cite:t:`Stern1994`.
.. jupyter-execute::
import matplotlib.pyplot as plt
import astropy.units as u
import astropy.visualization
import named_arrays as na
import optika
# Create a new instance of the e2v CCD97 light-sensitive material
material_ccd97 = optika.sensors.E2VCCD97Material()
# Store the wavelengths at which the QE was measured
wavelength_measured = material_ccd97.quantum_efficiency_measured.inputs
# Store the QE measurements
qe_measured = material_ccd97.quantum_efficiency_measured.outputs
# Define a grid of wavelengths with which to evaluate the fitted QE
wavelength_fit = na.geomspace(5, 10000, axis="wavelength", num=1001) * u.AA
# Evaluate the fitted QE using the given wavelengths
qe_fit = material_ccd97.quantum_efficiency_effective(
rays=optika.rays.RayVectorArray(
wavelength=wavelength_fit,
direction=na.Cartesian3dVectorArray(0, 0, 1),
),
normal=na.Cartesian3dVectorArray(0, 0, -1),
)
# Plot the measured QE vs the fitted QE
with astropy.visualization.quantity_support():
fig, ax = plt.subplots(constrained_layout=True)
na.plt.scatter(
wavelength_measured,
qe_measured,
label="measured",
)
na.plt.plot(
wavelength_fit,
qe_fit,
label="fit",
)
ax.set_xscale("log")
ax.set_xlabel(f"wavelength ({wavelength_fit.unit:latex_inline})")
ax.set_ylabel("quantum efficiency")
ax.legend()
The thickness of the oxide layer found by the fit is
.. jupyter-execute::
material_ccd97.thickness_oxide
The thickness of the implant layer found by the fit is
.. jupyter-execute::
material_ccd97.thickness_implant
The thickness of the substrate found by the fit is
.. jupyter-execute::
material_ccd97.thickness_substrate
And the differential charge collection efficiency at the backsurface
found by the fit is
.. jupyter-execute::
material_ccd97.cce_backsurface
"""

@property
def quantum_efficiency_measured(self) -> na.FunctionArray:
directory = pathlib.Path(__file__).parent
energy, qe = np.genfromtxt(
fname=directory / "e2v_ccd97_qe_moody2017.csv",
delimiter=", ",
unpack=True,
)
energy = energy << u.eV
wavelength = energy.to(u.AA, equivalencies=u.spectral())
return na.FunctionArray(
inputs=na.ScalarArray(wavelength, axes="wavelength"),
outputs=na.ScalarArray(qe, axes="wavelength"),
)

@functools.cached_property
def _quantum_efficiency_fit(self) -> dict[str, float | u.Quantity]:
qe_measured = self.quantum_efficiency_measured

unit_thickness_oxide = u.AA
unit_thickness_implant = u.AA
unit_thickness_substrate = u.um

def eqe_rms_difference(x: tuple[float, float, float, float]):
(
thickness_oxide,
thickness_implant,
thickness_substrate,
cce_backsurface,
) = x
qe_fit = quantum_efficiency_effective(
wavelength=qe_measured.inputs,
direction=na.Cartesian3dVectorArray(0, 0, 1),
thickness_oxide=thickness_oxide << unit_thickness_oxide,
thickness_implant=thickness_implant << unit_thickness_implant,
thickness_substrate=thickness_substrate << unit_thickness_substrate,
cce_backsurface=cce_backsurface,
)

return np.sqrt(np.mean(np.square(qe_measured.outputs - qe_fit))).ndarray

thickness_oxide_guess = 50 * u.AA
thickness_implant_guess = 2317 * u.AA
thickness_substrate_guess = 7 * u.um
cce_backsurface_guess = 0.21

fit = scipy.optimize.minimize(
fun=eqe_rms_difference,
x0=[
thickness_oxide_guess.to_value(unit_thickness_oxide),
thickness_implant_guess.to_value(unit_thickness_implant),
thickness_substrate_guess.to_value(unit_thickness_substrate),
cce_backsurface_guess,
],
method="nelder-mead",
)

thickness_oxide, thickness_implant, thickness_substrate, cce_backsurface = fit.x
thickness_oxide = thickness_oxide << unit_thickness_oxide
thickness_implant = thickness_implant << unit_thickness_implant
thickness_substrate = thickness_substrate << unit_thickness_substrate

return dict(
thickness_oxide=thickness_oxide,
thickness_implant=thickness_implant,
thickness_substrate=thickness_substrate,
cce_backsurface=cce_backsurface,
)

@property
def thickness_oxide(self) -> u.Quantity:
return self._quantum_efficiency_fit["thickness_oxide"]

@property
def thickness_implant(self) -> u.Quantity:
return self._quantum_efficiency_fit["thickness_implant"]

@property
def thickness_substrate(self) -> u.Quantity:
return self._quantum_efficiency_fit["thickness_substrate"]

@property
def cce_backsurface(self) -> float:
return self._quantum_efficiency_fit["cce_backsurface"]
15 changes: 15 additions & 0 deletions optika/sensors/_materials/_e2v_ccd97/_e2v_ccd97_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest
import optika
from .._materials_test import AbstractTestAbstractBackilluminatedCCDMaterial


@pytest.mark.parametrize(
argnames="a",
argvalues=[
optika.sensors.E2VCCD97Material(),
],
)
class TestE2VCCD97Material(
AbstractTestAbstractBackilluminatedCCDMaterial,
):
pass
33 changes: 33 additions & 0 deletions optika/sensors/_materials/_e2v_ccd97/e2v_ccd97_qe_moody2017.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
39.91891990062515, 0.6788079470198677
50.05075223132972, 0.7324503311258279
59.97856496789139, 0.8178807947019869
64.77313122091705, 0.8258278145695366
69.95096548922383, 0.8695364238410598
74.86231645365851, 0.8059602649006624
79.75688300572139, 0.8317880794701988
89.71162491843556, 0.851655629139073
97.76351712475721, 0.8576158940397353
109.96573146388018, 0.5317880794701989
130.00166645736314, 0.45033112582781476
149.57280983041403, 0.4860927152317882
199.79719692884856, 0.5635761589403975
249.37710648876907, 0.6529801324503313
309.8554473373301, 0.7225165562913909
399.1891990062509, 0.8357615894039737
500.50752231329665, 0.9052980132450333
547.9025729451029, 0.9211920529801326
568.0943005681785, 0.9271523178807949
599.7856496789133, 0.9291390728476823
647.7313122091703, 0.960927152317881
748.6231645365851, 0.9807947019867551
901.183765633706, 0.9986754966887419
1198.354915639953, 0.9907284768211922
1397.6006827213541, 0.8894039735099339
1593.5201660325251, 0.7741721854304637
1697.7059683717916, 0.6907284768211922
1800.5399029139885, 0.6569536423841061
1825.142004033248, 0.7463576158940399
1833.4171803334832, 0.8735099337748347
1833.4171803334832, 0.9172185430463577
1900.9836823956464, 0.947019867549669
1850.0802617566994, 0.9748344370860929
Loading

0 comments on commit 3807622

Please sign in to comment.