Processing EIGER images in CrystFEL - biochem-fan/cheetah GitHub Wiki

Processing EIGER images in CrystFEL

This has nothing to do with SACLA but may be useful for serial crystallography at synchrotrons.

Make a pixel mask

Save the following script as make_mask.py.

# 2024-Mar-01: updated for Python 3 and latest h5py
import h5py
import sys

if len(sys.argv) != 3 and len(sys.argv) != 4:
    print("Usage: %s master.h5 mask.h5 [dials_mask.pickle]" % sys.argv)
    sys.exit(-1)

f = h5py.File(sys.argv[1], "r")
mask = f["/entry/instrument/detector/detectorSpecific/pixel_mask"][()]
f.close()

if (len(sys.argv) == 4):
    import numpy as np
    import pickle

    m = pickle.load(open(sys.argv[3], "rb"))[0].as_numpy_array()
    mask[m == False] = 1

# Add more masks (horizontal lines in the middle of ASICs)
# these coordinates are for EIGER 16M

mask[255:261,:] = 1
mask[806:812,:] = 1
mask[1357:1363,:] = 1
mask[1908:1914,:] = 1
mask[2459:2465,:] = 1
mask[3010:3016,:] = 1
mask[3561:3566,:] = 1
mask[4112:4118,:] = 1

out = h5py.File(sys.argv[2], "w")
out["pixel_mask"] = mask
out.close()

Run this script on master.h5 to create a mask h5 file.

python make_mask.py col1_master.h5 mask.h5

If you need more complicated mask (e.g. rings), dials.generate_mask is useful. See Processing EIGER images by DIALS to learn how to use it. You can convert its output to the HDF5 format by

python make_mask.py col1_master.h5 mask.h5 mask.pickle

CrystFEL geometry file

Save the following as eiger.geom. Don't forget to replace photon_energy, adu_per_eV, clen, 0/corner_x and 0/corner_y with appropriate values.

photon_energy   = 12398 ; 12398 / (/entry/instrument/beam/incident_wavelength)
adu_per_eV      =  0.00008065 ; 1 / photon_energy
; adu_per_photon = 1 ; in future versions, you can specify like this
clen            = 0.500 ; /entry/instrument/detector/detector_distance
res             = 13333.3 ; 1 m / 75 um
max_adu         = 245900 ; /entry/instrument/detector/detectorSpecific/countrate_correction_count_cutoff

; used by geoptimiser
rigid_group_0 = 0
rigid_group_collection_0 = 0

mask_good = 0x00
mask_bad  = 0xFF

0/min_fs        = 0
0/max_fs        = 4149 ; /entry/instrument/detector/detectorSpecific/x_pixels_in_detector - 1
0/min_ss        = 0
0/max_ss        = 4370 ; /entry/instrument/detector/detectorSpecific/y_pixels_in_detector - 1
0/corner_x      = -1899.33 ; - /entry/instrument/detector/beam_center_x
0/corner_y      = -2187.37 ; - /entry/instrument/detector/beam_center_y
0/fs            = x
0/ss            = y

0/data = /entry/data/data
0/dim0 = %
0/dim1 = ss
0/dim2 = fs
0/mask = /pixel_mask
0/mask_file = mask.h5

If you have many datasets, the following script generate_eiger_geom.py might be useful:

# 2024-Oct-24: updated for Python 3, reordered items for newer CrystFEL
import h5py
import sys

if len(sys.argv) != 2:
    print("Usage: dials.python generate_eiger_geom.py master.h5 > eiger.geom")
    sys.exit(-1)

f = h5py.File(sys.argv[1], "r")

metadata = {}
metadata["photon_energy"] = 12398.0 / f["/entry/instrument/beam/incident_wavelength"][()]
metadata["adu_per_eV"] = 1 / metadata["photon_energy"]
metadata["clen"] = f["/entry/instrument/detector/detector_distance"][()]
metadata["max_adu"] = f["/entry/instrument/detector/detectorSpecific/countrate_correction_count_cutoff"][()]
metadata["det_x"] = int(f["/entry/instrument/detector/detectorSpecific/x_pixels_in_detector"][()]) - 1
metadata["det_y"] = int(f["/entry/instrument/detector/detectorSpecific/y_pixels_in_detector"][()]) - 1
metadata["center_x"] = - f["/entry/instrument/detector/beam_center_x"][()]
metadata["center_y"] = - f["/entry/instrument/detector/beam_center_y"][()]

f.close()


template = """\
photon_energy   = {photon_energy:.1f} ; 12398 / (/entry/instrument/beam/incident_wavelength)
adu_per_eV      = {adu_per_eV:.8f} ; 1 / photon_energy
; adu_per_photon = 1 ; in future versions, you can specify like this
clen            = {clen:.4f}; /entry/instrument/detector/detector_distance
res             = 13333.3 ; 1 m / 75 um
max_adu         = {max_adu:.1f} ; /entry/instrument/detector/detectorSpecific/countrate_correction_count_cutoff

; used by geoptimiser
rigid_group_0 = 0
rigid_group_collection_0 = 0

mask_good = 0x00
mask_bad  = 0xFF

0/min_fs        = 0
0/max_fs        = {det_x:d} ; /entry/instrument/detector/detectorSpecific/x_pixels_in_detector - 1
0/min_ss        = 0
0/max_ss        = {det_y:d} ; /entry/instrument/detector/detectorSpecific/y_pixels_in_detector - 1
0/corner_x      = {center_x:.2f} ; - /entry/instrument/detector/beam_center_x
0/corner_y      = {center_y:.2f} ; - /entry/instrument/detector/beam_center_y
0/fs            = x
0/ss            = y

0/data = /entry/data/data
0/dim0 = %
0/dim1 = ss
0/dim2 = fs
0/mask = /pixel_mask
0/mask_file = mask.h5
"""

print(template.format(**metadata))

CrystFEL input file list

Save the input file list as test.lst. Note that you have to specify data_NNNNNN.h5 files, not the master.h5 file.

col_1_data_000001.h5
col_1_data_000002.h5
col_1_data_000003.h5

Index and Integrate

Make sure that HDF5_PLUGIN_PATH points to the bitshuffle plugin. The plugin must be linked to the same version of HDF5 library used for CrystFEL. The easiest way is to install DIALS, build and run CrystFEL using HDF5 library that came with DIALS.

export LD_LIBRARY_PATH=$HOME/prog/dials/base/lib:$LD_LIBRARY_PATH
export HDF5_PLUGIN_PATH=$HOME/prog/dials/base/lib

indexamajig -g eiger.geom -i test.lst -o test.stream --indexing=mosflm \
 --int-radius=3,4,7 --peaks=zaef --min-gradient=30 --threshold=10 --min-snr=5

Best parameters depend on the dataset, of course. See Running CrystFEL to learn how to optimize parameters. Since the background was cleaner and spot intensites were weaker in this fine-sliced EIGER dataset than in MPCCD SFX images, the threshold and min-gradient are smaller here.

Indexing only hits

In addition to file names, you can specify image numbers in the input list.

col_1_data_000001.h5 //0
col_1_data_000001.h5 //30
col_1_data_000001.h5 //99
col_1_data_000002.h5 //0
col_1_data_000002.h5 //45

Note that image numbers start from 0 in CrystFEL. Since we have 100 frames in a file, this input instructs CrystFEL to process images 1, 31, 100, 101 and 146 (counting from 1).

We can write a script to make the list from Cheetah's log file frames.txt.

Why cannot we use master.h5 instead?

In principle, setting

0/data = /entry/data/%

in the geometry file should work. Unfortunately, CrystFEL complains:

All panels' data and mask entries must have the same number of placeholders.
Error: Data path ends with a / symbol

This is fixable, but we took the simplest approach in this tutorial.