"""
BINocular backend for beamline BM25, branch B first endstation [1]
This backend should serve as a basic implementation of a backend based on
xrayutilities [2]. It uses the information from the edf files (motors position
and detector image) ignoring the spec file, except for using its scan numbers
to identify images belonging to the same scan.
You should use CCD file names generated with the following pattern:
filename_#n_#p_#r.edf (n: spec-scan number, p: point number, r: image number)
Binning (2,2)
The backend is called 'EH2SCD'.
Created on 2014-10-28
[1] http://www.esrf.eu/UsersAndScience/Experiments/CRG/BM25/BeamLine/experimentalstations/Single_Crystal_Diffraction
[2] http://xrayutilities.sourceforge.net/
author: Dominik Kriegner (dominik.kriegner@gmail.com)
"""
import sys
import os
import glob
import numpy
import xrayutilities as xu
from .. import backend, errors, util
[docs]class HKLProjection(backend.ProjectionBase):
# scalars: mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, wavelength
# 3x3 matrix: UB
[docs] def project(
self,
mu,
theta,
phi,
chi,
ccdty,
ccdtx,
ccdtz,
ccdth,
ccdtr,
wavelength,
UB,
qconv,
):
qconv.wavelength = wavelength
h, k, l = qconv.area(
mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, UB=UB.reshape((3, 3))
)
return (h, k, l)
[docs] def get_axis_labels(self):
return "H", "K", "L"
[docs]class HKProjection(HKLProjection):
[docs] def project(
self,
mu,
theta,
phi,
chi,
ccdty,
ccdtx,
ccdtz,
ccdth,
ccdtr,
wavelength,
UB,
qconv,
):
H, K, L = super().project(
mu,
theta,
phi,
chi,
ccdty,
ccdtx,
ccdtz,
ccdth,
ccdtr,
wavelength,
UB,
qconv,
)
return (H, K)
[docs] def get_axis_labels(self):
return "H", "K"
[docs]class QProjection(backend.ProjectionBase):
[docs] def project(
self,
mu,
theta,
phi,
chi,
ccdty,
ccdtx,
ccdtz,
ccdth,
ccdtr,
wavelength,
UB,
qconv,
):
qconv.wavelength = wavelength
qx, qy, qz = qconv.area(
mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, UB=numpy.identity(3)
)
return (qx, qy, qz)
[docs] def get_axis_labels(self):
return "qx", "qy", "qz"
[docs]class QinpProjection(backend.ProjectionBase):
[docs] def project(
self,
mu,
theta,
phi,
chi,
ccdty,
ccdtx,
ccdtz,
ccdth,
ccdtr,
wavelength,
UB,
qconv,
):
qconv.wavelength = wavelength
qx, qy, qz = qconv.area(
mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, UB=numpy.identity(3)
)
return (numpy.sqrt(qx ** 2 + qy ** 2), qz)
[docs] def get_axis_labels(self):
return "qinp", "qz"
[docs]class EH2SCD(EDFInput):
monitor_counter = "C_mont"
# define BM25 goniometer, SIXC geometry? with 2D detector mounted on
# translation-axes
# see http://www.esrf.eu/UsersAndScience/Experiments/CRG/BM25/BeamLine/experimentalstations/Single_Crystal_Diffraction
# The geometry is: 4S + translations and one det. rotation
# sample axis: mu, th, chi, phi
# detector axis: translations + theta rotation (to make beam perpendicular
# to the detector plane in symmetric arrangement)
qconv = xu.experiment.QConversion(
["x+", "z+", "y+", "x+"], ["ty", "tx", "tz", "x+", "ty"], [0, 1, 0]
)
# convention for coordinate system: y downstream; x in bound; z upwards
# (righthanded)
# QConversion will set up the goniometer geometry. So the first argument
# describes the sample rotations, the second the detector rotations and the
# third the primary beam direction.
[docs] def parse_config(self, config):
super().parse_config(config)
centralpixel = self.config.centralpixel
# define detector parameters
roi = (
self.config.ymask[0],
self.config.ymask[-1] + 1,
self.config.xmask[0],
self.config.xmask[-1] + 1,
)
self.qconv.init_area(
"z-",
"x+",
cch1=centralpixel[1],
cch2=centralpixel[0],
Nch1=1912,
Nch2=3825,
pwidth1=self.config.pixelsize[1],
pwidth2=self.config.pixelsize[0],
distance=1e-10,
roi=roi,
)
print(
f"{' ':>20} {'Mu':>9} {'Theta':>10}"
f" {'CCD_Y':>9} {'CCD_X':>9} {'CCD_Z':>9}"
)
[docs] def process_image(self, image):
# motor positions
mu = float(image.header["M_mu"])
th = float(image.header["M_th"])
chi = float(image.header["M_chi"])
phi = float(image.header["M_phi"])
# distance 'ctr' corresponds to distance of the detector chip from
# the CCD_TH rotation axis. The rest is handled by the translations
ctr = -270.0 # measured by ruler only!!!
cty = float(image.header["M_CCD_Y"]) - self.config.sddy - ctr
ctx = float(image.header["M_CCD_X"]) - self.config.sddx
ctz = float(image.header["M_CCD_Z"]) - self.config.sddz
cth = float(image.header["M_CCD_TH"]) - self.config.ccdth0
# filter correction
transm = 1.0 # no filter correction! (Filters are manual on BM25!)
mon = float(image.header[self.monitor_counter])
wavelength = float(image.header["WAVELENGTH"])
if self.config.UB:
UB = self.config.UB
else:
UB = self._get_UB(image.header)
# normalization
data = image.data / mon / transm
print(
f"{os.path.split(image.filename)[-1]:>20}"
f" {mu:9.4f} {th:10.4f} {cty:9.1f} {ctx:9.1f} {ctz:9.1f}"
)
# masking
intensity = self.apply_mask(data, self.config.xmask, self.config.ymask)
return (
intensity,
numpy.ones_like(intensity),
(
mu,
th,
phi,
chi,
cty,
ctx,
ctz,
cth,
ctr, ## weights added to API. Treated here like before
wavelength,
UB,
self.qconv,
),
)
@staticmethod
def _get_UB(header):
ub = numpy.zeros(9)
for i in range(9):
ub[i] = float(header[f"UB{i:d}"])
return ub