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()
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
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")
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]:
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]:
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]:
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')
Out[12]: