metrology
getModulePosition(metrologyFile, moduleId)
¶
Position (in mm) of a module relative to the top left corner of its quadrant. In case of tile-level positions, the the position refers to the center of the top left pixel.
Args¶
str
Fully qualified path and filename of the metrology file
str
Identifier of the module in question (e.g. 'Q1M2T03')
Returns¶
ndarray
(x, y)-Position of the module in its quadrant
Raises¶
In case the moduleId contains invalid module
identifieres
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/metrology.py
def getModulePosition(metrologyFile, moduleId):
"""Position (in mm) of a module relative to the top left
corner of its quadrant. In case of tile-level positions,
the the position refers to the center of the top left
pixel.
Args
----
metrologyFile : str
Fully qualified path and filename of the metrology file
moduleId : str
Identifier of the module in question (e.g. 'Q1M2T03')
Returns
-------
ndarray:
(x, y)-Position of the module in its quadrant
Raises
------
ValueError: In case the moduleId contains invalid module
identifieres
"""
# The moduleId does not directly appear as a dataset in the
# metrology file. Instread, it follows the nomenclature of
# the detector. LPD-style module identifier have the format
#
# QXMYTZZ
#
# where X, Y, and Z are digits. Q denotes the quadrant
# (X = 1, ..., 4), M the supermodule (Y = 1, ..., 4) and T
# the tile (Z = 1, ..., 16; with leading zeros).
modulePattern = re.compile(r'[QMT]\d+')
# Give the module identifier Q1M1T01, the moduleList splits this
# into the associated quadrant, supermodule, and tile identifiers:
# >>> print(moduleList)
# ['Q1', 'M1', 'T01']
moduleList = modulePattern.findall(moduleId)
# The metrology file is stored in hdf5 format. It stores positions
# hierarchally, starting on the supermodule level. the h5Keys list
# contains all path that will be accessed in the hdf5 file
# >>> print(h5Keys)
# ['Q1', 'Q1/M1', 'Q1/M1/T01']
h5Keys = ['/'.join(moduleList[:idx+1]) for idx in range(len(moduleList))]
# Every module of the detector gives its position relative to
# the top left corner of its parent structure. Every position
# is stored in the positions array
positions = []
# Access file
with h5py.File(metrologyFile, "r") as fh:
# Check if the keys actually appear in the metrology file
for key in h5Keys:
if key not in fh:
raise ValueError("Invalid key '{}'".format(key))
# Extract the positions from the hdf5 groups corresponding
# to a module, if the module has dataset 'Position'.
positions = [
np.asarray(fh[key]['Position']) for key in h5Keys if 'Position' in fh[key]
]
if not positions:
# This is the case when requesting a quadrant; e.g.
# getModulePosition('Q1'). Key is valid, but quadrant
# has no location (yet).
positions = [[0.0, 0.0]]
# Convert to numpy array
positions = np.asarray(positions)
# Return the sum of all positions retrieved
return positions.sum(axis=0)
plotSupermoduleData(tileData, metrologyPositions, zoom=1.0, vmin=100.0, vmax=6000.0)
¶
Plots data of a supermodule with tile positions determined by the metrology data.
Parameters¶
ndarray
Supermodule image data separated in individual tiles. Must have shape (16, 32, 128).
ndarray
Tile positions as retrieved from the metrology file. Must have shape (16, 2)
float, optional
Can enlarge or decrease the size of the plot. Default = 1.
vmin, vmax : float, optional Value range. Default vmin=100., vmax=6000.
Returns¶
matplotlib.Figure Figure object containing the supermodule image
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/metrology.py
def plotSupermoduleData(tileData, metrologyPositions, zoom=1., vmin=100., vmax=6000.):
"""Plots data of a supermodule with tile positions
determined by the metrology data.
Parameters
----------
tileData : ndarray
Supermodule image data separated in individual tiles.
Must have shape (16, 32, 128).
metrologyPositions : ndarray
Tile positions as retrieved from the metrology file.
Must have shape (16, 2)
zoom : float, optional
Can enlarge or decrease the size of the plot. Default = 1.
vmin, vmax : float, optional
Value range. Default vmin=100., vmax=6000.
Returns
-------
matplotlib.Figure
Figure object containing the supermodule image
"""
# Data needs to have 16 tiles, each with
# 32x128 pixels
assert tileData.shape == (16, 32, 128)
# Conversion coefficient, required since
# matplotlib does its business in inches
mmToInch = 1./25.4 # inch/mm
# Some constants
numberOfTiles = 16
numberOfRows = 8
numberOfCols = 2
tileWidth = 65.7 # in mm
tileHeight = 17.7 # in mm
# Base width and height are given by spatial
# extend of the modules. The constants 3.4 and 1
# are estimated as a best guess for gaps between
# modules.
figureWidth = zoom * numberOfCols*(tileWidth + 3.4)*mmToInch
figureHeight = zoom * numberOfRows*(tileHeight + 1.)*mmToInch
fig = plt.figure(figsize=(figureWidth, figureHeight))
# The metrology file references module positions
bottomRightCornerCoordinates = translateToModuleBL(metrologyPositions)
# The offset here accounts for the fact that there
# might be negative x,y values
offset = np.asarray(
[min(bottomRightCornerCoordinates[:, 0]),
min(bottomRightCornerCoordinates[:, 1])]
)
# Account for blank borders in the plot
borderLeft = 0.5 * mmToInch
borderBottom = 0.5 * mmToInch
# The height and width of the plot remain
# constant for a given supermodule
width = zoom * 65.7 * mmToInch / (figureWidth - 2.*borderLeft)
height = zoom * 17.7 * mmToInch / (figureHeight - 2.*borderBottom)
for i in range(numberOfTiles):
# This is the top left corner of the tile with
# respect to the top left corner of the supermodule
x0, y0 = bottomRightCornerCoordinates[i] - offset
# Transform to figure coordinates
ax0 = borderLeft + zoom * x0 * mmToInch / (figureWidth - 2.*borderLeft)
ay0 = borderBottom + zoom * y0 * mmToInch / (figureHeight - 2.*borderBottom)
# Place the plot in the figure
ax = fig.add_axes((ax0, ay0, width, height), frameon=False)
# Do not display axes, tick markers or labels
ax.tick_params(
axis='both', left='off', top='off', right='off', bottom='off',
labelleft='off', labeltop='off', labelright='off', labelbottom='off'
)
# Plot the image. If one wanted to have a colorbar
# the img object would be needed to produce one
img = ax.imshow(
tileData[i],
interpolation='nearest',
vmin=vmin, vmax=vmax
)
return fig
splitChannelDataIntoTiles(channelData, clockwiseOrder=False)
¶
Splits the raw channel data into indiviual tiles
Args¶
ndarray
Raw channel data. Must have shape (256, 256)
bool, optional
If set to True, the sequence of tiles is given in the clockwise order starting with the top right tile (LPD standard). If set to false, tile data is returned in reading order
Returns¶
ndarray Same data, but reshaped into (12, 32, 128)
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/metrology.py
def splitChannelDataIntoTiles(channelData, clockwiseOrder=False):
"""Splits the raw channel data into indiviual tiles
Args
----
channelData : ndarray
Raw channel data. Must have shape (256, 256)
clockwiseOrder : bool, optional
If set to True, the sequence of tiles is given
in the clockwise order starting with the top
right tile (LPD standard). If set to false, tile
data is returned in reading order
Returns
-------
ndarray
Same data, but reshaped into (12, 32, 128)
"""
tiles = np.asarray(np.split(channelData, 2, axis=1))
tiles = np.asarray(np.split(tiles, 8, axis=1))
orderedTiles = tiles.reshape(16, 32, 128)
if clockwiseOrder:
# Naturally, the tile data after splitting is in reading
# order (i.e. top left tile is first, top right tile is second,
# etc.). The official LPD tile order however is clockwise,
# starting with the top right tile. The following array
# contains indices of tiles in reading order as they would
# be iterated in clockwise order (starting from the top right)
readingOrderToClockwise = [1,3,5,7,9,11,13,15,14,12,10,8,6,4,2,0]
# Return tiles in reading order
orderedTiles = orderedTiles[readingOrderToClockwise]
return orderedTiles
splitChannelDataIntoTiles2(channelData, clockwiseOrder=False)
¶
Splits the raw channel data into indiviual tiles
Args¶
ndarray
Raw channel data. Must have shape (256, 256)
bool, optional
If set to True, the sequence of tiles is given in the clockwise order starting with the top right tile (LPD standard). If set to false, tile data is returned in reading order
Returns¶
ndarray Same data, but reshaped into (12, 32, 128)
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/metrology.py
def splitChannelDataIntoTiles2(channelData, clockwiseOrder=False):
"""Splits the raw channel data into indiviual tiles
Args
----
channelData : ndarray
Raw channel data. Must have shape (256, 256)
clockwiseOrder : bool, optional
If set to True, the sequence of tiles is given
in the clockwise order starting with the top
right tile (LPD standard). If set to false, tile
data is returned in reading order
Returns
-------
ndarray
Same data, but reshaped into (12, 32, 128)
"""
tiles = np.asarray(np.split(channelData, 8, axis=1))
tiles = np.asarray(np.split(tiles, 2, axis=1))
orderedTiles = np.moveaxis(tiles.reshape(16, 128, 32, channelData.shape[2]), 2, 1)
if clockwiseOrder:
# Naturally, the tile data after splitting is in reading
# order (i.e. top left tile is first, top right tile is second,
# etc.). The official LPD tile order however is clockwise,
# starting with the top right tile. The following array
# contains indices of tiles in reading order as they would
# be iterated in clockwise order (starting from the top right)
readingOrderToClockwise = list(range(8,16))+list(range(7,-1,-1))
# Return tiles in reading order
orderedTiles = orderedTiles[readingOrderToClockwise]
return orderedTiles
translateToModuleBL(tilePositions)
¶
Tile coordinates within a supermodule with the origin in the bottom left corner.
Parameters¶
ndarray
Tile positions as retrieved from the LPD metrology file. Must have shape (16, 2)
Returns¶
ndarray Tile positions relative to the bottom left corner.
Source code in /usr/src/app/checkouts/readthedocs.org/user_builds/european-xfel-offline-calibration/envs/latest/lib/python3.8/site-packages/cal_tools/metrology.py
def translateToModuleBL(tilePositions):
"""Tile coordinates within a supermodule with the
origin in the bottom left corner.
Parameters
----------
tilePositions : ndarray
Tile positions as retrieved from the LPD metrology
file. Must have shape (16, 2)
Returns
-------
ndarray
Tile positions relative to the bottom left corner.
"""
assert tilePositions.shape == (16, 2)
tileHeight = 17.7 # mm
# The module origin is the top left corner of the
# top left tile. So in the clockwise order of LPD
# tiles, it is the last tile in the list
moduleOrigin = tilePositions[-1]
# np.asarray([1., -1.]): inverts the y-axis, since
# matplotlib.figure coordinate system is on the
# bottom left pointing up and to the right
moduleCoords = np.asarray([1., -1.])*(
# np.asarray([0., tileHeight]): Tile positions
# are translated from the top left corner to the
# bottom left corner, i.e. 0 in x, tileHeight in y
tilePositions + np.asarray([0., tileHeight]) - moduleOrigin
)
# In the clockwise order of LPD tiles, the 8th
# tile in the list is the bottom left tile
bottomLeft8th = np.asarray([0., moduleCoords[8][1]])
return moduleCoords - bottomLeft8th