def __init__( self, lam=1.3785e-10, # wavelength, meters, corresponding to 9 keV pix=55.e-6, # detector pixel size, meters arm=0.65, # sample-detector distance, meters gamma=45., # degrees delta=25., # degrees dtheta=0.01, # degrees (this becomes the step in phi if delta = 0). best_orthogonality=False, # leave as False unless you know what you're doing! rocking_motor='th', # Options are 'th', 'ph'. recipSpaceSteps=[256, 256, 70] # data array size ): """Initialize object with default values with: sg = ScatteringGeometry() or with any set of values you want, eg: sg = ScatteringGeometry( lam=2.64e-10, arm=0.6, peak=[ 2, 0, 0 ], a0=2.32e-10 ) All the parameters you don't specify explicitly will be initialized to default values. 'rocking_motor' manually sets the rocking direction: theta motor if 'th' and phi motor if 'ph'. Only works if 'best_orthogonality' is set to False. The 'best_orthogonality' parameter, when True, computes the mutual orthogonality parameter separately for a theta rocking and a phi rocking, and chooses the better one. Doesn't apply to experimental data, where the sampling is decided during the measurement, but helps to simulate more well-behaved Fourier volumes in BCDI. Don't set to True if you're reconstructing scans whose experimental geometry is already known. If False, a rocking direction of 'th' is assumed unless specified otherwise. """ rocking_dict = {'th': misc.Delta, 'ph': misc.Gamma} self._lambda = lam self._pix = pix self._arm = arm self._delta = delta * np.pi / 180. self._gamma = gamma * np.pi / 180. self._ki = (1. / self._lambda) * np.array([0., 0., 1.]).reshape( -1, 1) # incident unit vector (3x1) self._kf = misc.Delta(self._delta) @ misc.Gamma( self._gamma) @ self._ki # scattered wave vector (3x1) self._Q = self._kf - self._ki self.detectorFrame = misc.Delta(self._delta) @ misc.Gamma(self._gamma) detXY = self._pix / (self._lambda * self._arm) * self.detectorFrame @ np.array( [[1., 0., 0.], [0., 1., 0.]]).T # the columns of this matrix are the reciprocal space steps in the detector x and y directions. self._dtheta = dtheta * np.pi / 180. self._recip = np.array(recipSpaceSteps).astype(float).reshape(1, -1) Brange = np.diag(1. / self._recip.ravel()) if best_orthogonality == False: self._Mtheta = rocking_dict[rocking_motor] # this is the 3x1 direction of changing Q. # Also, the NEGATIVE of the detector translation step. self._dq = (self._Mtheta(self._dtheta) @ self._Q) - self._Q self._Brecip = np.concatenate((detXY, -1. * self._dq), axis=1) # The columns of this matrix are the sampling steps in 3 independent directions in reciprocal space. # Two of these are perpendicular (i.e. detector plane) while the third is the rocking direction. else: # used in simulations to choose the best orthogonal sampling of signal _dq = [(_Mtheta(self._dtheta) @ self._Q) - self._Q for _Mtheta in [misc.Gamma, misc.Delta]] _Brecip = [np.concatenate((detXY, -1. * q), axis=1) for q in _dq] _ortho = [np.absolute(misc.orthogonality(Bq)) for Bq in _Brecip] _best = np.where(_ortho == max(_ortho))[0][0] self._dq = _dq[_best] self._Brecip = _Brecip[_best] self._Breal = np.linalg.inv(self._Brecip).T @ Brange # The columns of this matrix are the sampling directions in 3D real space. self._Brecip *= 1.e-9 # scaling to nm^-1 units self._Breal *= 1.e+9 # scaling to nm units self.ortho = [misc.orthogonality(M) for M in self.getSamplingBases()] # these two numbers should be as close to 1 as possible for better simulations. return
def __init__( self, lam=1.3785e-10, # wavelength, meters, corresponding to 9 keV pix=55.e-6, # detector pixel size, meters arm=0.65, # sample-detector distance, meters gamma=45., # degrees delta=25., # degrees dtheta=0.01, # degrees (this becomes the step in phi if delta = 0). recipSpaceSteps=[256, 256, 70] # data array size ): """Initialize object with default values with: sg = ScatteringGeometry() or with any set of values you want, eg: sg = ScatteringGeometry( lam=2.64e-10, arm=0.6, peak=[ 2, 0, 0 ], a0=2.32e-10 ) All the parameters you don't specify explicitly will be initialized to default values. """ self._lambda = lam self._pix = pix self._arm = arm self._delta = delta * np.pi / 180. self._gamma = gamma * np.pi / 180. self._ki = (1. / self._lambda) * np.array([0., 0., 1.]).reshape( -1, 1) # incident unit vector (3x1) self._kf = misc.Delta(self._delta) @ misc.Gamma( self._gamma) @ self._ki # scattered wave vector (3x1) self._Q = self._kf - self._ki self._dtheta = dtheta * np.pi / 180. self._recip = np.array(recipSpaceSteps).astype(float).reshape(1, -1) if self._delta == 0.: # scattering in the straight-up direction; phi motor is being rocked self._Mtheta = misc.Gamma else: # scattering off to the side; theta motor is being rocked self._Mtheta = misc.Delta self._dq = (self._Mtheta(self._dtheta) @ self._Q) - self._Q # this is the 3x1 direction of changing Q. # Also, the NEGATIVE of the detector translation step. self.detectorFrame = misc.Delta(self._delta) @ misc.Gamma(self._gamma) detXY = self._pix / ( self._lambda * self._arm ) *\ self.detectorFrame @\ np.array( [ [ 1., 0., 0. ], [ 0., 1., 0. ] ] ).T # the columns of this matrix are the reciprocal space steps in the detector x and y directions. self._Brecip = np.concatenate((detXY, -1. * self._dq), axis=1) # The columns of this matrix are the sampling steps in 3 independent directions in reciprocal space. # Two of these are perpendicular (i.e. detector plane) while the third is the rocking direction. Brange = np.diag(1. / self._recip.ravel()) self._Breal = np.linalg.inv(self._Brecip).T @ Brange # The columns of this matrix are the sampling directions in 3D real space. self._Brecip *= 1.e-9 # scaling to nm^-1 units self._Breal *= 1.e+9 # scaling to nm units