The incident flux and the current that is generated by a photodiode subjected to it are related by
$$ \begin{equation} \begin{split} I(A)=&\sum_{i,j}P_{i,j}(W)R_{j}(A/W)+D(A)\\ P_{i,j}(W)=&I_{i,j}(Hz)E_{j}(\text{keV})\\ R_{j}(A/W)=&\frac{e(C)}{E_{h}(\text{keV})}[1-e^{-\mu(E_{j})\rho d}] \end{split} \end{equation} $$where P the incident power, R the spectral responsivity, D the dark current, $E_i$ the energy of the incident photon, $E_j$ the energy of the detected photon, $E_{h}$ the energy to create an electron-hole pair, $I_{i,j}$ the detected flux of line $j$ due to line $i$ and diode density $\rho$, mass attenuation coefficient $\mu$ and thickness $d$.
The relationship between the detected flux and the flux at the sample position is given by
$$ \begin{equation} I_{i,j}(Hz)=I_{0}(Hz) w_i Y_{i,j} = I_{s}(Hz)\frac{w_i Y_{i,j}}{\sum_k w_k T_{s}(E_{k})} \end{equation} $$with the following factors
The line fractions at the sample position are
$$ \begin{equation} \begin{split} I_{i,s}=& I_0 w_i T_{s}(E_{i})\\ w_{i,s} =& \frac{I_{i,s}}{\sum_k I_{k,s}} = \frac{w_i T_{s}(E_{i})}{\sum_k w_k T_{s}(E_{k})} \end{split} \end{equation} $$The relationship between the flux reaching the sample and the current measured by a pn-diode can be summarized as
$$ \begin{equation} \begin{split} I(A)=&I_{s}(Hz)C_s(C)+D(A)\\ C_s(C) =& \frac{\sum_{i,j} w_i Y_{i,j}C_j}{\sum_k w_k T_{s}(E_{k})}\\ C_j(C) =& E_{j}(\text{keV})\frac{e(C)}{E_{h}(\text{keV})}[1-e^{-\mu(E_{j})\rho d}]\\ \end{split} \end{equation} $$where $C_s$ the charge generated per photon reaching the sample and $C_j$ the charge generated per photon reaching the diode. A simplified relationship with a lookup table can be used
$$ \begin{equation} C_s(C) = \sum_i w_i \mathrm{LUT}(E_i) \end{equation} $$Finally in order to allow a fast read-out, current is converted to frequency by an oscillator
$$ \begin{equation} I(\text{Hz})=\frac{F_{\text{max}}(Hz)}{V_{\text{max}}(V)} \frac{V_{\text{max}}(V)}{I_{\text{max}}(A)}I(A)+F_{0}(Hz) \end{equation} $$where $F_{\text{max}}$ the maximal frequency that can be detected, $F_{0}$ a fixed offset, $V_{\text{max}}$ the maximal output voltage of the ammeter and input voltage of the oscillator, $\frac{V_{\text{max}}(V)}{I_{\text{max}}(A)}$ the "gain" of the ammeter. Sometimes $I_{\text{max}}(A)$ is referred to as the diode "gain".
In [1]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from spectrocrunch.detectors import diode
det = diode.factory("sxm_ptb")
print(det)
det.model = False
energy = det.menergy
R = det.spectral_responsivity(energy)
plt.plot(energy,R,marker='o',linestyle="",label='measured')
det.model = True
energy = np.linspace(1,10,100)
R = det.spectral_responsivity(energy)
plt.plot(energy,R,label='model')
plt.legend()
plt.xlabel('Energy (keV)')
plt.ylabel('Spectral responsivity (A/W)')
plt.show()
In [2]:
det = diode.factory("sxm_idet",npop=4)
print(det)
det.model = False
energy = det.menergy
R = det.spectral_responsivity(energy)
plt.plot(energy,R,marker='o',linestyle="",label='measured')
det.model = True
energy = np.linspace(1,12,100)
R = det.spectral_responsivity(energy)
plt.plot(energy,R,label='model')
det.model = False
R = det.spectral_responsivity(energy)
plt.plot(energy,R,label='used')
plt.legend(loc='best')
plt.xlabel('Energy (keV)')
plt.ylabel('Spectral responsivity (A/W)')
plt.show()
When $Y_{i,j}$ only contains transmission factors (i.e. not fluorescence/scattering from a secondary target) the diode measures $I_s$ directly. The relationship between flux $I_s(Hz)$ and diode response $I(Hz)$ is determined by the spectral responsivity which should be known (absolute diode) or calibrated (with respect to an absolute diode):
In [3]:
from spectrocrunch.optics import xray as xrayoptics
airpath = xrayoptics.Filter(material="air",thickness=1)
det = diode.factory("sxm_idet",optics=[airpath])
print(det)
Is = 3e9
energy = np.linspace(2,10,100)
for gain in [1e7]:
for model in [False,True]:
det.gain = gain
det.model = model
I = [det.fluxtocps(en,Is).to("Hz").magnitude for en in energy]
plt.plot(energy,I,label="{:.0e} V/A{}".\
format(gain," (model)" if model else ""))
plt.gca().axhline(y=det.oscillator.Fmax.to("Hz").magnitude,label="Fmax",\
color='k',linestyle='--')
plt.title("Flux = {:.0e} Hz".format(Is))
plt.legend(loc='best')
plt.xlabel('Energy (keV)')
plt.ylabel('Response (Hz)')
plt.show()
The conversion factors $Y_{i,j}$ can be calculated from the cross-sections of the secondary target.
In [4]:
iodet = diode.factory("sxm_iodet1",optics=[airpath])
iodet.gain = 1e9
print(iodet.geometry)
Is = 5e9
energy = np.linspace(1,10,50)
I = [iodet.fluxtocps(en,Is).to("Hz").magnitude for en in energy]
plt.plot(energy,I)
plt.gca().axhline(y=iodet.oscillator.Fmax.to("Hz").magnitude,label="Fmax",\
color='k',linestyle='--')
plt.title("Flux = {:.0e} Hz".format(Is))
plt.xlabel('Energy (keV)')
plt.ylabel('Response (Hz)')
plt.show()
In case the indirect diode is upstream from the optics, transmission $T_s$ needs to be calibrated with a direct diode. This is done by measuring a changing flux at fixed energy, e.g. by scanning a series of attenuators. The flux is calculated from the direct diode and used to calibrate the response of the indirect diode:
In [5]:
iodet = diode.factory("sxm_iodet1",optics=[airpath,"KB"])
iodet.gain = 1e8
idet = diode.factory("sxm_idet")
idet.gain = 1e6
energy = 7
idetresp = np.linspace(3e4,5e4,100)
fluxmeas = idet.cpstoflux(energy,np.random.poisson(idetresp))
iodetresp = np.random.poisson(np.linspace(2e5,3e5,100))
fitinfo = iodet.calibrate(iodetresp,fluxmeas,energy,caliboption="optics")
print(iodet.geometry)
plt.plot(fluxmeas,iodetresp,marker='o',linestyle="")
plt.plot(fluxmeas,iodet.fluxtocps(energy,fluxmeas))
label = "\n".join(["{} = {}".format(k,v) for k,v in fitinfo.items()])
plt.annotate(label,xy=(0.5,0.1),xytext=(0.5,0.1),\
xycoords="axes fraction",textcoords="axes fraction")
plt.title("Gain = {:~.0e}".format(iodet.gain))
plt.xlabel('Flux (Hz)')
plt.ylabel('Response (Hz)')
plt.show()
Note that the slope is $C_s(C)$ (the charge generated per photon reaching the sample, expressed here in units of elementary charge) and the intercept $D(A)$ (the dark current of the diode).
In [6]:
#Specify quantities manually with units:
#from spectrocrunch.patch.pint import ureg
#current = ureg.Quantity(1e-8,"A")
for simple in [True,False]:
iodet = diode.factory("sxm_iodet1",optics=[airpath,"KB"],simplecalibration=simple)
iodet.gain = 1e8
# Calibrate with Hz-Hz pair
cps = 100000
flux = 1e9
energy = 6
iodet.calibrate(cps,flux,energy,caliboption="optics")
current = iodet.fluxtocurrent(energy,flux)
# Calibrate with A-Hz pair
energy = 10
current *= 0.5
iodet.calibrate(current,flux,energy,caliboption="optics")
label = "C$_s$ table" if simple else "Calibrated T$_s$"
print(label)
print(iodet)
print("")
energy = np.linspace(6,10,10)
response = [iodet.fluxtocps(en,flux).magnitude for en in energy]
plt.plot(energy,response,label=label)
plt.legend()
plt.title("Gain = {:~.0e}, Flux = {:.0e}".format(iodet.Rout,flux))
plt.xlabel('Energy (keV)')
plt.ylabel('Response (Hz)')
plt.show()