Assembling detector data into images

The X-ray detectors at XFEL are made up of a number of small pieces. To get an image from the data, or analyse it spatially, we need to know where each piece is located.

This example reassembles some commissioning data from LPD, a detector which has 4 quadrants, 16 modules, and 256 tiles. Elements (especially the quadrants) can be repositioned; talk to the detector group to ensure that you have the right geometry information for your data.

[1]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import h5py

from karabo_data import RunDirectory, stack_detector_data
from karabo_data.geometry2 import LPD_1MGeometry
[2]:
run = RunDirectory('/gpfs/exfel/exp/FXE/201830/p900020/proc/r0221/')
run.info()
# of trains:    513
Duration:       0:00:51.200000
First train ID: 54861753
Last train ID:  54862265

14 detector modules (FXE_DET_LPD1M-1)
  e.g. module FXE_DET_LPD1M-1 0 : 256 x 256 pixels
  128 frames per train, 39040 total frames

0 instrument sources (excluding detectors):

0 control sources:

[3]:
# Find a train with some data in
empty = np.asarray([])
for tid, train_data in run.trains():
    module_imgs = sum(d.get('image.data', empty).shape[0] for d in train_data.values())
    if module_imgs:
        print(tid, module_imgs)
        break
54861797 1792
[4]:
tid, train_data = run.train_from_id(54861797)
print(tid)
for dev in sorted(train_data.keys()):
    print(dev, end='\t')
    try:
        print(train_data[dev]['image.data'].shape)
    except KeyError:
        print("No image.data")
54861797
FXE_DET_LPD1M-1/DET/0CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/10CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/11CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/12CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/13CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/14CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/15CH0:xtdf  (128, 256, 256)
FXE_DET_LPD1M-1/DET/1CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/2CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/3CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/4CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/6CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/8CH0:xtdf   (128, 256, 256)
FXE_DET_LPD1M-1/DET/9CH0:xtdf   (128, 256, 256)

Extract the detector images into a single Numpy array:

[5]:
modules_data = stack_detector_data(train_data, 'image.data')
modules_data.shape
[5]:
(128, 16, 256, 256)

To show the images, we sometimes need to ‘clip’ extreme high and low values, otherwise the colour map makes everything else the same colour.

[6]:
def clip(array, min=-10000, max=10000):
    x = array.copy()
    finite = np.isfinite(x)
    # Suppress warnings comparing numbers to nan
    with np.errstate(invalid='ignore'):
        x[finite & (x < min)] = np.nan
        x[finite & (x > max)] = np.nan
    return x
[7]:
plt.figure(figsize=(10, 5))

a = modules_data[5][2]
plt.subplot(1, 2, 1).hist(a[np.isfinite(a)])

a = clip(a, min=-400, max=400)
plt.subplot(1, 2, 2).hist(a[np.isfinite(a)]);
_images/apply_geometry_9_0.png

Let’s look at the iamge from a single module. You can see where it’s divided up into tiles:

[8]:
plt.figure(figsize=(8, 8))
clipped_mod = clip(modules_data[10][2], -400, 500)
plt.imshow(clipped_mod, origin='lower')
[8]:
<matplotlib.image.AxesImage at 0x2b611fe49390>
_images/apply_geometry_11_1.png

Here’s a single tile:

[9]:
splitted = LPD_1MGeometry.split_tiles(clipped_mod)
plt.figure(figsize=(8, 8))
plt.imshow(splitted[11])
[9]:
<matplotlib.image.AxesImage at 0x2b611feb5080>
_images/apply_geometry_13_1.png

Load the geometry from a file, along with the quadrant positions used here.

In the future, geometry information will be stored in the calibration catalogue.

[10]:
# From March 18; converted to XFEL standard coordinate directions
quadpos = [(11.4, 299), (-11.5, 8), (254.5, -16), (278.5, 275)]  # mm

geom = LPD_1MGeometry.from_h5_file_and_quad_positions('lpd_mar_18_axesfixed.h5', quadpos)

Reassemble and show a detector image using the geometry:

[11]:
geom.plot_data_fast(clip(modules_data[12], max=5000))
[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x2b611ff3de48>
_images/apply_geometry_17_1.png

Reassemble detector data into a numpy array for further analysis. The areas without data have the special value ``nan`` to mark them as missing.

[12]:
res, centre = geom.position_modules_fast(modules_data)
print(res.shape)
plt.figure(figsize=(8, 8))
plt.imshow(clip(res[12, 250:750, 450:850], min=-400, max=5000), origin='lower')
(128, 1203, 1105)
[12]:
<matplotlib.image.AxesImage at 0x2b60ec9f4160>
_images/apply_geometry_19_2.png