AGIPD

The Adaptive Gain Integrating Pixel Detector is a high speed detector intended for use at the European XFEL. It allows single pulse imaging at 4.5 MHz frame rate with a dynamic range allowing single photon detection and detection of more than 10.000 12.4 keV photons in the same image.

AGIPD

The AGIPD camera consists of four individually moveable quadrants, each having four detector tiles with 512 × 128 pixels per tile, giving a total of 1024 × 1024, or roughly 1 million pixels. https://doi.org/10.1107/S1600577518016077

In this tutorial exposes a way to fit the gaps of the the 4 quadrants. Data courtesy of Johannes Möller, Desy/EuXFEL.

Display the data for SAXS

Considering the displacement of the modules...

In [1]:
%matplotlib notebook
import os
import numpy
from matplotlib.pyplot import subplots
import pyFAI
from pyFAI.azimuthalIntegrator import AzimuthalIntegrator
from pyFAI.distortion import Distortion
from pyFAI.gui import jupyter
import time
t0 = time.time()
In [2]:
#I got some better data from my colleagues. It's silicon titanate measured at 
#10.235 keV.
In [3]:
#Spatial distortion calculated:
spatial_distortion = numpy.load('AGIPDdistortion.npy')
print(spatial_distortion.dtype, spatial_distortion.shape)
float64 (1024, 1024, 4, 3)
In [4]:
#setup pyFAI azimuthal integrator with spatial distortion for AGIPD1M.  

agipd = pyFAI.detectors.Detector(200e-6, 200e-6)
agipd.aliases = ["AGIPD1M"]
agipd.shape = (1024,1024)
agipd.mask = numpy.zeros((1024,1024))
agipd.set_pixel_corners(spatial_distortion)


ai = AzimuthalIntegrator(detector=agipd)
ai.setFit2D(5500,621,649)
ai.wavelength = 1.33e-10
In [5]:
#Display the module position in space
fig, ax = subplots()
ax.scatter(spatial_distortion[:,:,1,2],spatial_distortion[:,:,1,1],marker=",")
ax.set_title('spatial distortion')
Out[5]:
Text(0.5, 1.0, 'spatial distortion')
In [6]:
saxs_data = numpy.load('agipd_saxs.npy')
jupyter.display(saxs_data)
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5cc05da0>
In [7]:
agipd.distortion_correction = Distortion(agipd,resize=True)
plotdata = agipd.distortion_correction.correct(saxs_data)

jupyter.display(plotdata)
Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5cb4cf28>
In [8]:
# Save the detector definition
dest = "agipd.h5"
if os.path.exists(dest):
    os.unlink(dest)
agipd.save(dest)
In [9]:
powder = numpy.load("agipd_powder.npy")
In [10]:
jupyter.display(powder.clip(0,500))
Out[10]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c3119e8>
In [11]:
det = pyFAI.detector_factory("agipd.h5")
/home/kieffer/VirtualEnv/py37/lib/python3.7/site-packages/h5py/_hl/dataset.py:313: H5pyDeprecationWarning: dataset.value has been deprecated. Use dataset[()] instead.
  "Use dataset[()] instead.", H5pyDeprecationWarning)
In [12]:
abs(det.get_pixel_corners()-agipd.get_pixel_corners()).max()
Out[12]:
0.0
In [13]:
cor = Distortion(detector=det, resize = True)
In [14]:
powder2 = powder.copy()
powder2[numpy.logical_not(numpy.isfinite(powder))] = 0
powder2[powder<0] = 0
In [15]:
dis = cor.correct(powder2)
jupyter.display(cor.correct(powder2).clip(0, 500))
Out[15]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c3417f0>
In [16]:
mask = numpy.logical_not(numpy.logical_and(numpy.isfinite(powder), powder>0))
maska  = numpy.ones(agipd.shape)
maska[:512, :512] = 0
QA = numpy.logical_or(mask, maska)
agipd.mask = QA
agipd.save('agipd_A.h5')
jupyter.display(QA, label="Quadrant A")
Out[16]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c2991d0>
In [17]:
maska  = numpy.ones(agipd.shape)
maska[:512, 512:] = 0
QA = numpy.logical_or(mask, maska)
agipd.mask = QA
agipd.save('agipd_B.h5')
jupyter.display(QA, label="Quadrant B")
Out[17]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c24a208>
In [18]:
maska  = numpy.ones(agipd.shape)
maska[512:, 512:] = 0
QA = numpy.logical_or(mask, maska)
agipd.mask = QA
agipd.save('agipd_C.h5')
jupyter.display(QA, label="Quadrant C")
Out[18]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c2db630>
In [19]:
maska  = numpy.ones(agipd.shape)
maska[512:, :512] = 0
QA = numpy.logical_or(mask, maska)
agipd.mask = QA
agipd.save('agipd_D.h5')
jupyter.display(QA, label="Quadrant D")
Out[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c238278>

Claibration of every single quadrant separatly

Each quadrant is calibrated separately. The PONI and the control points are saved accordingly:

In [20]:
from pyFAI import goniometer
from pyFAI.control_points import ControlPoints

class AgipdTrans(goniometer.BaseTransformation):
    def __init__(self):
        goniometer.BaseTransformation.__init__(self, None, param_names = ["dist", "rot1", "rot2", "rot3", 
                                                                          "A1", "A2", "B1", "B2", 
                                                                          "C1", "C2", "D1", "D2"],)
        self.codes = {}
    def __call__(self,param, pos):
        "Actually pos is the quadrant label"
        return goniometer.PoniParam(param[0],
                                    param[(ord(pos)-65)*2+4],
                                    param[(ord(pos)-65)*2+5],
                                    param[1],
                                    param[2],
                                    param[3])
agipd_trans = AgipdTrans()
In [21]:
cal = pyFAI.calibrant.Calibrant("Li4Ti5O12.D")
wavelength=1.21137466924e-10
cal.wavelength=wavelength

ai_A = pyFAI.load("cas_A.poni")
ai_B = pyFAI.load("cas_B.poni")
ai_C = pyFAI.load("cas_C.poni")
ai_D = pyFAI.load("cas_D.poni")
cp_A = ControlPoints("cas_A.npt")
cp_B = ControlPoints("cas_B.npt")
cp_C = ControlPoints("cas_C.npt")
cp_D = ControlPoints("cas_D.npt")

meta_detector = goniometer.GoniometerRefinement([ai_A.dist, 0, 0, 0, 
                                                ai_A.poni1, ai_A.poni2,
                                                ai_B.poni1, ai_B.poni2,
                                                ai_C.poni1, ai_C.poni2,
                                                ai_D.poni1, ai_D.poni2],
                                                trans_function=agipd_trans,
                                                pos_function=(lambda a:a),
                                                detector = "agipd.h5",
                                                
                                                wavelength=wavelength
                                               )
ERROR:pyFAI.control_points:ControlPoints.load: unable to convert to float Li4Ti5O12 Calibrant with 11 reflections at wavelength 1.21137466924e-10 (wavelength): 'NoneType' object has no attribute 'set_wavelength'
ERROR:pyFAI.control_points:ControlPoints.load: unable to convert to float Li4Ti5O12 Calibrant with 11 reflections at wavelength 1.21137466924e-10 (wavelength): 'NoneType' object has no attribute 'set_wavelength'
ERROR:pyFAI.control_points:ControlPoints.load: unable to convert to float Li4Ti5O12 Calibrant with 11 reflections at wavelength 1.21137466924e-10 (wavelength): 'NoneType' object has no attribute 'set_wavelength'
ERROR:pyFAI.control_points:ControlPoints.load: unable to convert to float Li4Ti5O12 Calibrant with 11 reflections at wavelength 1.21137466924e-10 (wavelength): 'NoneType' object has no attribute 'set_wavelength'
In [22]:
meta_detector.new_geometry("A", powder, "A", cp_A, cal, ai_A)
meta_detector.new_geometry("B", powder, "B", cp_B, cal, ai_B)
meta_detector.new_geometry("C", powder, "C", cp_C, cal, ai_C)
meta_detector.new_geometry("D", powder, "D", cp_D, cal, ai_D)
Out[22]:
<pyFAI.goniometer.SingleGeometry at 0x7fac5c1ee550>
In [23]:
meta_detector.residu2(meta_detector.param)
Out[23]:
2.757168171216403e-06
In [24]:
#meta_detector.single_geometries["A"].get_position()
meta_detector.trans_function(meta_detector.param, "D")
Out[24]:
PoniParam(dist=0.130447896236, poni1=0.130027139644, poni2=0.11304205831, rot1=0, rot2=0, rot3=0)
In [25]:
res = meta_detector.refine2()
Cost function before refinement: 2.757168171216403e-06
[0.1304479  0.         0.         0.         0.1267334  0.13303017
 0.12805689 0.13412486 0.12957062 0.11239513 0.13002714 0.11304206]
     fun: 2.419429022011655e-06
     jac: array([ 4.75336378e-07, -1.38212556e-07, -3.76084586e-08,  7.67386155e-13,
       -2.76933378e-07,  3.07575363e-07, -4.79938222e-07, -2.32440783e-07,
        3.62421702e-07, -2.82746896e-07, -1.49489097e-08,  8.97204387e-07])
 message: 'Optimization terminated successfully.'
    nfev: 274
     nit: 19
    njev: 19
  status: 0
 success: True
       x: array([ 1.30483487e-01, -4.55998275e-04,  1.57790178e-03, -4.72663049e-11,
        1.26474972e-01,  1.32976587e-01,  1.27833952e-01,  1.33998264e-01,
        1.29417045e-01,  1.12432991e-01,  1.29689354e-01,  1.13005712e-01])
Cost function after refinement: 2.419429022011655e-06
GonioParam(dist=0.13048348652650854, rot1=-0.00045599827503712127, rot2=0.0015779017801245735, rot3=-4.7266304876129446e-11, A1=0.1264749717253987, A2=0.13297658690463837, B1=0.12783395214201343, B2=0.13399826374148313, C1=0.12941704504102072, C2=0.11243299120748738, D1=0.12968935442116444, D2=0.11300571219034442)
maxdelta on: rot2 (2) 0 --> 0.0015779017801245735
In [26]:
#Reconstruction of the detector
det = pyFAI.detector_factory("agipd.h5")
new_distortion = det._pixel_corners[...]
A_min1 = new_distortion[:512, :512,:,1].min()
A_min2 = new_distortion[:512, :512,:,2].min()
#A
new_distortion[:512, :512,:,1] -= A_min1
new_distortion[:512, :512,:,2] -= A_min2
#B
new_distortion[:512, 512:,:,1] += res[4] - res[6] - A_min1 
new_distortion[:512, 512:,:,2] += res[5] - res[7] - A_min2 
#C
new_distortion[512:, 512:,:,1] += res[4] - res[8] - A_min1 
new_distortion[512:, 512:,:,2] += res[5] - res[9] - A_min2 
#D
new_distortion[512:, :512,:,1] += res[4] - res[10] - A_min1
new_distortion[512:, :512,:,2] += res[5] - res[11] - A_min2 
In [27]:
import copy
agipd2 = copy.copy(det)
agipd2.set_pixel_corners(new_distortion)
agipd2.save("agipd_fitted.h5")
In [28]:
cor2 = Distortion(detector=agipd2, resize=True)
jupyter.display(cor2.correct(powder2).clip(0,1000))
Out[28]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fac5c0b4240>

Conclusion

This tutorial explains how to fit multiple sub-detector together and to combine them together into a single one.

In [29]:
print("Total execution time:", time.time()-t0)
Total execution time: 7.836239576339722