Selection of a calibrant

In this tutorial we will see how to select a calibrant for a given experimental setup.

Experimental setup

The experimental setup is a classical protein crystallography setup with:

  • Large Pilatus 6M detector on a translation table
  • The small and intense beam of ~50 microns in size has a wavelength of 1 Angstrom
  • The detector is in normal condition: orthogonal to the beam and centered in the middle of the detector.

The scientist in charge of this beamline want to ensure all encoders are working properly and needs to validate the setup for distances between 10cm and 80cm. He will buy reference material from NIST but he does not know which calibrant is best suited for his setup. We will assume all reference material sold by NIST are equally suitable for ray position (no issue with grain size, ...).

The calibration works best in pyFAI if more than one Debye-Scherrer ring is seen on the detector.

Define the detector

import time
start_time = time.time()
import pyFAI
print("PyFAI version" + pyFAI.version)
dete = pyFAI.detectors.Pilatus6M()
print(dete)
PyFAI version0.14.0-dev4
Detector Pilatus 6M  PixelSize= 1.720e-04, 1.720e-04 m

Select reference materials

The NIST sells different Standard Refrence Materials, among them Silicon (SRM640), Lanthanum hexaboride (SRM660), Alumina (SRM676) and Ceria (SRM674) are commonly used. Many other exists: Cr203, TiO2, Zn0, SiO2, ... Evaluating them is left as an exercise.

import pyFAI.calibrant
print(pyFAI.calibrant.ALL_CALIBRANTS)
Calibrants available: Au, LaB6_SRM660c, CrOx, CuO, Cr2O3, Si_SRM640c, CeO2, AgBh, cristobaltite, TiO2, PBBA, C14H30O, Al, LaB6, NaCl, ZnO, mock, LaB6_SRM660a, Si_SRM640a, LaB6_SRM660b, alpha_Al2O3, Si_SRM640e, Si_SRM640, Si_SRM640b, quartz, Ni, Si, Si_SRM640d

You may wonder where the names of the calibrant came from and how they have been established.

The name of all calibrant available in your version of pyFAI can be listed by just printing out the content of ALL_CALIBRANTS. New calibrant may have been added in pyFAI in more recent releases, just have a look at the developent web page.

Most of those calibrant files, which contain the d-spacing in Angstrom between Miller plans, have been prepared from the unit cell of the compount, found in publication. This publication is referenced in the header of the file. If one wishes to regenerate those files, the pyFAI.calibrant.Cell class may be used for.

We will now focus on a subset of calibrant, instanciate them and put them into a dictionnary. The Python construct used here is called dict-comprehension and allows the creation and population of a dictionnary in a single line.

cals = dict((name,pyFAI.calibrant.ALL_CALIBRANTS(name)) for name in ("Si", "LaB6", "CeO2", "alpha_Al2O3"))
print(cals)
{'alpha_Al2O3': alpha_Al2O3 Calibrant , 'Si': Si Calibrant , 'CeO2': CeO2 Calibrant , 'LaB6': LaB6 Calibrant }

To be able to use those calibrants, one needs to define the wavelength used, here 1 Angstrom.

wl = 1e-10
for cal in cals.values():
    cal.wavelength = wl
print(cals)
{'alpha_Al2O3': alpha_Al2O3 Calibrant at wavelength 1e-10, 'Si': Si Calibrant at wavelength 1e-10, 'CeO2': CeO2 Calibrant at wavelength 1e-10, 'LaB6': LaB6 Calibrant at wavelength 1e-10}

Short distance images

The shortest the detector can come to the sample is about 10cm (to leave space for the beamstop). We will generate images of diffraction at this distance.

For the display of images we will use matplotlib inlined and some utilities from pyFAI to display images.

p1, p2, p3 = dete.calc_cartesian_positions()
poni1 = p1.mean()
poni2 = p2.mean()
print("Detector center at %s, %s"%(poni1, poni2))
ai_short = pyFAI.AzimuthalIntegrator(dist=0.1, poni1=poni1, poni2=poni2,detector=dete)
print(ai_short)
Detector center at 0.217322, 0.211818
Detector Pilatus 6M  PixelSize= 1.720e-04, 1.720e-04 m
SampleDetDist= 1.000000e-01m        PONI= 2.173222e-01, 2.118181e-01m       rot1=0.000000  rot2= 0.000000  rot3= 0.000000 rad
DirectBeamDist= 100.000mm   Center: x=1231.500, y=1263.501 pix      Tilt=0.000 deg  tiltPlanRotation= 0.000 deg
%pylab nbagg
from pyFAI.gui import jupyter
Populating the interactive namespace from numpy and matplotlib
fig, ax = subplots(2, 2, figsize=(10,10))
for idx, key in enumerate(cals):
    cal = cals[key]
    img = cal.fake_calibration_image(ai_short)
    jupyter.display(img, label=key, ax=ax[idx//2, idx%2])
<IPython.core.display.Javascript object>

As one can see, there are plenty of rings on the image: it should be easy to calibrate. By moving further away the detector, the number of rings will decrease.

Long distance images

To keep good calibration one should have at lease two rings for the calibration. The longest distance from sample to the detector is 80cm.

ai_long = pyFAI.AzimuthalIntegrator(dist=0.8, poni1=poni1, poni2=poni2, detector=dete)
print(ai_long)
Detector Pilatus 6M  PixelSize= 1.720e-04, 1.720e-04 m
SampleDetDist= 8.000000e-01m        PONI= 2.173222e-01, 2.118181e-01m       rot1=0.000000  rot2= 0.000000  rot3= 0.000000 rad
DirectBeamDist= 800.000mm   Center: x=1231.500, y=1263.501 pix      Tilt=0.000 deg  tiltPlanRotation= 0.000 deg
fig, ax = subplots(2, 2, figsize=(10,10))
for idx, key in enumerate(cals):
    cal = cals[key]
    img = cal.fake_calibration_image(ai_long)
    jupyter.display(img, label=key, ax=ax[idx//2, idx%2])
<IPython.core.display.Javascript object>

The most adapted calibrant is probably the LaB6 as 2 rings are still visible at 80 cm from the detector.

Integration of the pattern for the two extreme cases

We can integrate the image for the two extreme cases:

lab6 = cals["LaB6"]
ai_short.wavelength = ai_long.wavelength = wl

fig, ax = subplots(2, 2, figsize=(10,10))
img_short = lab6.fake_calibration_image(ai_short)
jupyter.display(img_short, label="LaB6 d=0.1m", ax=ax[0,0])
jupyter.plot1d(ai_short.integrate1d(img_short,1000), ax=ax[0,1])

img_long = lab6.fake_calibration_image(ai_long)
jupyter.display(img_long, label="LaB6 d=0.8m", ax=ax[1,0])
jupyter.plot1d(ai_long.integrate1d(img_long,1000), ax=ax[1,1])
<IPython.core.display.Javascript object>
<matplotlib.axes._subplots.AxesSubplot at 0x7fa82229bd30>

Conclusion

The best calibrant in this case is probably LaB6.

print("Total execution time: %.3fs"%(time.time()-start_time))
Total execution time: 16.044s