Tutorial 1: Experimental Parameters

1. Detector Parameters

1.1 PONI File

The pygid.ExpParams class stores the experimental parameters of the detector and the X-ray wavelength. These parameters are needed to calculate the reciprocal-space coordinates of each pixel on the area detector.

The easiest way to load them is using a PONI file, similar to the pyFAI library [https://pyfai.readthedocs.io].

When you create an ExpParams instance, it reads the PONI file from the poni_path you provide. It loads the position of the point of normal incidence (in meters) and the detector rotation angles (in radians). You can create a PONI file from a calibrant image using the pyFAI-calib2 GUI: [https://www.silx.org/doc/pyFAI/latest/usage/cookbook/calib-gui/index.html].

The pixel size is set automatically based on the detector. If your detector is custom or unknown, provide px_size in meters.

Important: Make sure the detector orientation is set to 3. For grazing-incidence experiments, also set the angle of incidence (ai) in degrees.

Before using the detector parameters, download the example files from Zenodo:

from pygid.datasets import get_dataset

# Download example dataset from Zenodo
try:
    files = get_dataset("tutorial_01")
    poni_path = files["poni"]
    mask_path = files["mask"]
except:
    print("Dataset download skipped on Read the Docs.")
WARNING - Matplotlib is building the font cache; this may take a moment.
INFO - Failed to extract font properties from /usr/share/fonts/truetype/noto/NotoColorEmoji.ttf: Can not load face (unknown file format; error code 0x2)
INFO - generated new fontManager

Load experimental parameters from PONI:

import pygid
params = pygid.ExpParams(
    poni_path=poni_path,            # path to the PONI file
    ai = 0.01,                       # angle of incidence (in degrees)
)
print(params)
ExpParams(poni_path='/home/docs/.cache/pygid/tutorial_01/LaB6_2024_07_ESRF_ID10.poni', mask_path=None, mask=None, flipud=False, fliplr=False, transp=False, px_size=7.5e-05, img_dim=None, n=None, SDD=0.3271661836504515, wavelength=0.6199209921660013, rot1=-0.0026308680554001304, rot2=-0.004647717026373591, rot3=0.0, poni1=0.1446949272569577, poni2=0.14877525660652482, centerX=None, centerY=None, k=None, count_range=None, ai=0.01, scan=None)

Note: The class is associated with a single geometry; therefore, separate class instances must be created for different PONI files. For incident-angle scans, see Tutorial 7.

1.2 Direct Loading

Instead of using a PONI file, you can also load the detector parameters manually. You need to provide:

  • Sample-to-detector distance SDD (in meters)

  • Point of normal incidence poni1 (vertical) and poni2 (horizontal) in meters

  • Detector rotation angles rot1, rot2, rot3 (in radians)

  • Pixel size px_size (in meters)

  • Wavelength wavelength (in angstroms)

  • Angle of incidence ai (in degrees)

import pygid

params = pygid.ExpParams(
    SDD=0.327,          # Sample-to-detector distance (m)
    wavelength=0.6199,  # Wavelength (Å)
    rot1=-0.0026,       # Detector rotation X (radians)
    rot2=-0.0046,       # Detector rotation Y (radians)
    rot3=0,             # Detector rotation Z (radians)
    poni1=0.145,        # Beam vertical position (m)
    poni2=0.149,        # Beam horizontal position (m)
    px_size=75e-6,      # Detector pixel size (m)
    ai=0.01             # Angle of incidence (degrees)
)
print(params)
ExpParams(poni_path=None, mask_path=None, mask=None, flipud=False, fliplr=False, transp=False, px_size=7.5e-05, img_dim=None, n=None, SDD=0.327, wavelength=0.6199, rot1=-0.0026, rot2=-0.0046, rot3=0, poni1=0.145, poni2=0.149, centerX=None, centerY=None, k=None, count_range=None, ai=0.01, scan=None)

Instead of poni1 and poni2, you can also provide centerX and centerY in pixels (relative to the bottom-left corner):

import pygid

params = pygid.ExpParams(
    SDD=0.327,          # Sample-to-detector distance (m)
    wavelength=0.6199,  # Wavelength (Å)
    rot1=-0.0026,       # Detector rotation X (radians)
    rot2=-0.0046,       # Detector rotation Y (radians)
    rot3=0,             # Detector rotation Z (radians)
    centerX=1909,       # Beam vertical position (m)
    centerY=1996,       # Beam horizontal position (m)
    px_size=75e-6,      # Detector pixel size (m)
    ai=0.01             # Angle of incidence (degrees)
)
print(params)
ExpParams(poni_path=None, mask_path=None, mask=None, flipud=False, fliplr=False, transp=False, px_size=7.5e-05, img_dim=None, n=None, SDD=0.327, wavelength=0.6199, rot1=-0.0026, rot2=-0.0046, rot3=0, poni1=None, poni2=None, centerX=1909, centerY=1996, k=None, count_range=None, ai=0.01, scan=None)

2. Masking

2.1 Mask from File

You can load a “static” detector mask to remove detector gaps, dead or hot pixels, and the beam stop shadow. Provide the path to the mask file (mask_path) in any supported format: EDF, TIFF, or NPY. Pixels with value 1 in the mask will be replaced with numpy.nan.

import pygid
params = pygid.ExpParams(
    poni_path=poni_path,
    mask_path=mask_path,
    ai=0.01
)
print(params.mask.shape)
(2162, 2068)

2.2 Mask as a NumPy Array

If the mask is already loaded as a NumPy array, you can pass it directly to ExpParams:

import pygid
import numpy as np

mask_array = np.load(mask_path) # numpy array with the mask

params = pygid.ExpParams(
    poni_path=poni_path,
    mask=mask_array,
    ai=0.01
)
print(params.mask.shape)
(2162, 2068)

2.3 Dynamic Mask

A “dynamic” mask can be used to mask very strong or very weak reflections that vary from image to image. To use it, provide count_range as a tuple (min, max) defining the intensity range. The dynamic mask can be used together with or instead of the static mask:

import pygid

params = pygid.ExpParams(
    poni_path=poni_path,
    mask_path=mask_path,
    count_range=(10, 10000),  # intensity range for dynamic masking
    ai=0.01
)

3. Image Flipping & Transposition

In some cases, the detector image must be flipped or transposed prior to conversion. The following boolean parameters control these operations:

  • fliplr = True — horizontal flipping (left-to-right)

  • flipud = True — vertical flipping (upside-down)

  • transp = True — transposition (swap of X and Y axes) — can be used when the detector was rotated

These operations modify the detector orientation and therefore alter the corresponding experimental geometry:

  • point of normal incidence:

if transp:
    poni1, poni2 = poni2, poni1
if flipud:
    poni1 = img_dim[0] * px_size - poni1
if self.fliplr:
    poni2 = (img_dim[1] - 1) * px_size - poni2

where img_dim - image dimensions; px_size - pixel size

  • detector rotation angles:

if transp:
    rot1, rot2 = -rot2, -rot1
if flipud:
    rot2 = -rot2
    rot3 = -rot3
if fliplr:
    rot1 = -rot1
    rot3 = -rot3

The effect of these options on the conversion result is illustrated below:

pygid

The correct combination is highlighted in green.

In particular cases, when rot3 (rotation around the beam) in non-zero, rot3 changes the sign when flipud or fliplr are used.

Note: rot3 0 and transp = True should not be used simultaneously, as this leads to an ambiguous detector orientation.

After loading the experimental parameters, you can calculate the coordinate maps. This is covered in Tutorial 2.