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.


In [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

In [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:


In [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

In [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:


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


Out[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.


In [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

In [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)]);


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


In [8]:
plt.figure(figsize=(8, 8))
clipped_mod = clip(modules_data[10][2], -400, 500)
plt.imshow(clipped_mod, origin='lower')


Out[8]:
<matplotlib.image.AxesImage at 0x2ab829b330b8>

Here's a single tile:


In [9]:
splitted = LPD_1MGeometry.split_tiles(clipped_mod)
plt.figure(figsize=(8, 8))
plt.imshow(splitted[11])


Out[9]:
<matplotlib.image.AxesImage at 0x2ab8693f9358>

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.


In [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:


In [11]:
geom.plot_data_fast(clip(modules_data[12], max=5000))


Out[11]:

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


In [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)
Out[12]:
<matplotlib.image.AxesImage at 0x2ab869b09da0>