CsPad Geometry

The geometry of the CSPad (Cornell SLAC Pixel Array Detector) is described elsewhere, but briefly, the exact alignment of each 2x1 (section) in each quad differs from detector to detector (there are 3 currently) and (potentially) from experiment to experiment. Optical measurements of the detector metrology has been made for each detector configuration (CSPad metrology and calibration files, links), and cspad data array alignment have been worked out on the basis of these (2011-08-10 XPP CSPAD alignment parameters), sufficient for conveniently displaying the full detector image.

This document describes a different approach: The data pixels are typically stored in terms of quad-number (0-4), section-number (0-8), row and column numbers (185 x 2*194). We can compute the x,y,z coordinates of each of these pixels individually, based on the optical meterology measurements.

XtcExplorer

This is in the XtcExplorer, in its cspad utility class CsPad. The function that computes the coordinates is called once, at class creation time, but could equally well be loaded from a numpy array (perhaps we'll do this in the future). For now this serves as an example of how to compute this pixel coordinate maps.

There's currently no function that uses these coordinates for plotting. The pixel coordinate map is mostly useful if you want to find the location of individual spots, e.g. Bragg peaks, in your data. You can then get the x-coordinate from the x_coordinates array using the same indices that you use to get the data value. I.e. you find a peak of interest in quad#2, section#4, row 33, column 102, you'll find the x and y-coordinate from x_coordinates[2,4,33,102] and y_coordinates[2,4,33,102].

The coordinates

The coordinates of the optical measurements are defined by X, Y, Z, where (0,0,0) is at the nominal beam spot, and X points along the long side of section 1, Y points towards section 3 and Z points downstream. The unit of the measurements are micrometers, but the computed pixel coordinates here have been converted to pixel units (based on the average effective pixel size). Each quad coordinate system is rotated by 90 degrees w.r.t. the next one.

The coordinates we try to extract here are the coordinates in the final image, with x-coordinate represented by the image columns and y-coordinates representing the rows. Here, the row numbering runs in the negative y-direction.

The method (python code)

Here's almost cut'n'paste from the latest XtcExplorer/src/cspad.py. Feel free to use it, and let me know if you have suggestions for improvements.

import numpy as np
import scipy.ndimage.interpolation as interpol

x_coordinates = np.zeros((4,8,185,388), dtype="float")
y_coordinates = np.zeros((4,8,185,388), dtype="float")
z_coordinates = np.zeros((4,8,185,388), dtype="float")

def get_asics(bigsection):
    """Utility function
    @param bigsection   a 185 x 391 section for plotting (has a 3-pixel gap between asics)
    @return asics       the 185 x 388 section array which has the 3-pixel gap removed
    """
    asic0 = bigsection[:,0:194]
    asic1 = bigsection[:,(391-194):]
    asics = np.concatenate( (asic0,asic1), axis=1 )
    return asics

# section pixel array / grid
rr,cc = np.mgrid[0:185:185j, 0:391:391j]

# now compute the "fractional pixels"
rrfrac = rr / 185.0
ccfrac = cc / 391.0

# remove the 3-pixel gap
rrfrac = get_asics(rrfrac)
ccfrac = get_asics(ccfrac)

sec_coords = np.array([rrfrac,ccfrac])

# load data from metrology file (ignore first column)
metrology = np.loadtxt("XtcExplorer/calib/CSPad/cspad_2011-08-10-Metrology.txt")[:,1:]
metrology = metrology.reshape(4,8,4,3)

# also, we need to resort the 2x1 sections, they are
# listed in the file in the order 1,0,3,2,4,5,7,6
metrology = metrology[:,(1,0,3,2,4,5,7,6),:,:]

# I want the points in a different order: 
# (firstrow,firstcol), (firstrow,lastcol), (lastrow,firstcol), (lastrow,lastcol)
sec_coord_order = [(1,2,0,3),(1,2,0,3),(2,3,1,0),(2,3,1,0),(3,0,2,1),(3,0,2,1),(2,3,1,0),(2,3,1,0)]

# collect all pixel widths for averaging
dLong = np.zeros((4,8,2), dtype="float64")   # pixel size from long side of section
dShort = np.zeros((4,8,2), dtype="float64")  # pixel size from short side of section

# Start looping over quads and sections:

for quad in range(4):

    for sec in range(8):

        # corner values
        input_x = metrology[quad,sec,sec_coord_order[sec],0].reshape(2,2)
        input_y = metrology[quad,sec,sec_coord_order[sec],1].reshape(2,2)
        input_z = metrology[quad,sec,sec_coord_order[sec],2].reshape(2,2)

        # interpolate coordinates over to the pixel map
        x_coordinates[quad,sec] = interpol.map_coordinates(input_x, sec_coords)
        y_coordinates[quad,sec] = interpol.map_coordinates(input_y, sec_coords)
        z_coordinates[quad,sec] = interpol.map_coordinates(input_z, sec_coords)

        # ! in micrometers! Need to convert to pixel units
        dL = np.array([ abs(input_x[0,1]-input_x[0,0])/391,
                        abs(input_x[1,1]-input_x[1,0])/391,
                        abs(input_y[0,0]-input_y[0,1])/391,
                        abs(input_y[1,0]-input_y[1,1])/391 ])
        dLong[quad,sec] = dL[dL>100] # filter out the nonsense ones

        dS = np.array([ abs(input_y[0,0]-input_y[1,0])/185,
                        abs(input_y[0,1]-input_y[1,1])/185,
                        abs(input_x[0,0]-input_x[1,0])/185,
                        abs(input_x[0,1]-input_x[1,1])/185 ])
        dShort[quad,sec] = dS[dS>100] # filter out the nonsense ones

dTotal = np.concatenate( (dLong.ravel(), dShort.ravel() ))
print "Pixel-size:"
print "     long side average:    %.2f +- %.2f "%( dLong.mean(), dLong.std())
print "     short side average:   %.2f +- %.2f "%( dShort.mean(), dShort.std())
print "     all sides average:    %.2f +- %.2f "%( dTotal.mean(), dTotal.std())

# use the total to convert it all to pixel units
x_coordinates = x_coordinates / dTotal.mean()
y_coordinates = y_coordinates / dTotal.mean()
z_coordinates = z_coordinates / dTotal.mean()

# Now we have the pixel coordinates in the metrology (quadrant) coordinate system. 
# need to convert this to image coordinates, where x and y is swapped for two of the
# quadrants: 

origin = [[834,834],[834,834],[834,834],[834,834]]
for quad in range(4):
    # For each quad, rotate and shift into the image coordinate system
    if quad==0 :
        savex = np.array( self.x_coordinates[quad] )
        self.x_coordinates[quad] = origin[quad][0] - self.y_coordinates[quad]
        self.y_coordinates[quad] = origin[quad][1] - savex
    if quad==1 :
        self.x_coordinates[quad] = origin[quad][0] + self.x_coordinates[quad]
        self.y_coordinates[quad] = origin[quad][1] - self.y_coordinates[quad]
    if quad==2 :
        savex = np.array( self.x_coordinates[quad] )
        self.x_coordinates[quad] = origin[quad][0] + self.y_coordinates[quad]
        self.y_coordinates[quad] = origin[quad][1] + savex
    if quad==3 :
       self.x_coordinates[quad] = origin[quad][0] - self.x_coordinates[quad]
       self.y_coordinates[quad] = origin[quad][1] + self.y_coordinates[quad]

print "Done making coordinate map of the CSPAD detector."

The result (coordinate maps for x,y,z and plots)

Here, the coordinate map for x, y and z have been plotted as a CsPad image (the tile position might be slightly off), thus the intensity in the image is coordinate values, and you can see the blue end of the spectrum are low coordinate values and the red end of the spectrum are high coordinate values. The plots serve as a check that the coordinate map is correct. The x and y coordinate plots helps understand the coordinate system for these measurements. The z coordinate plot has been included for completeness, but show only a slight variation and is not actually used for anything at this point.

Intermediate result: The quadrant (metrology) coordinate system in pixel units:

x and y coordinates (pixel units)

x, y, z coordinates (pixel units)

x, y, z coordinates (microns)

Final result: The image coordinate system in pixel units:

  • No labels