def __init__(self): # wavelength self.wave = Neutron() # sample self.sample = Sample() # aperture self.aperture = Aperture() # detector self.detector = Detector() # 2d image of the resolution self.image = [] self.image_lam = [] # resolutions # lamda in r-direction self.sigma_lamd = 0 # x-dir (no lamda) self.sigma_1 = 0 #y-dir (no lamda) self.sigma_2 = 0 # 1D total self.sigma_1d = 0 self.gravity_phi = None # q min and max self.qx_min = -0.3 self.qx_max = 0.3 self.qy_min = -0.3 self.qy_max = 0.3 # q min and max of the detector self.detector_qx_min = -0.3 self.detector_qx_max = 0.3 self.detector_qy_min = -0.3 self.detector_qy_max = 0.3 # possible max qrange self.qxmin_limit = 0 self.qxmax_limit = 0 self.qymin_limit = 0 self.qymax_limit = 0 # plots self.plot = None # instrumental params defaults self.mass = 0 self.intensity = 0 self.wavelength = 0 self.wavelength_spread = 0 self.source_aperture_size = [] self.source2sample_distance = [] self.sample2sample_distance = [] self.sample_aperture_size = [] self.sample2detector_distance = [] self.detector_pix_size = [] self.detector_size = [] self.get_all_instrument_params() # max q range for all lambdas self.qxrange = [] self.qyrange = []
class ResolutionCalculator(object): """ compute resolution in 2D """ def __init__(self): # wavelength self.wave = Neutron() # sample self.sample = Sample() # aperture self.aperture = Aperture() # detector self.detector = Detector() # 2d image of the resolution self.image = [] self.image_lam = [] # resolutions # lamda in r-direction self.sigma_lamd = 0 # x-dir (no lamda) self.sigma_1 = 0 #y-dir (no lamda) self.sigma_2 = 0 # 1D total self.sigma_1d = 0 self.gravity_phi = None # q min and max self.qx_min = -0.3 self.qx_max = 0.3 self.qy_min = -0.3 self.qy_max = 0.3 # q min and max of the detector self.detector_qx_min = -0.3 self.detector_qx_max = 0.3 self.detector_qy_min = -0.3 self.detector_qy_max = 0.3 # possible max qrange self.qxmin_limit = 0 self.qxmax_limit = 0 self.qymin_limit = 0 self.qymax_limit = 0 # plots self.plot = None # instrumental params defaults self.mass = 0 self.intensity = 0 self.wavelength = 0 self.wavelength_spread = 0 self.source_aperture_size = [] self.source2sample_distance = [] self.sample2sample_distance = [] self.sample_aperture_size = [] self.sample2detector_distance = [] self.detector_pix_size = [] self.detector_size = [] self.get_all_instrument_params() # max q range for all lambdas self.qxrange = [] self.qyrange = [] def compute_and_plot(self, qx_value, qy_value, qx_min, qx_max, qy_min, qy_max, coord='cartesian'): """ Compute the resolution : qx_value: x component of q : qy_value: y component of q """ # make sure to update all the variables need. # except lambda, dlambda, and intensity self.get_all_instrument_params() # wavelength etc. lamda_list, dlamb_list = self.get_wave_list() intens_list = [] sig1_list = [] sig2_list = [] sigr_list = [] sigma1d_list = [] num_lamda = len(lamda_list) for num in range(num_lamda): lam = lamda_list[num] # wavelength spread dlam = dlamb_list[num] intens = self.setup_tof(lam, dlam) intens_list.append(intens) # cehck if tof if num_lamda > 1: tof = True else: tof = False # compute 2d resolution _, _, sigma_1, sigma_2, sigma_r, sigma1d = \ self.compute(lam, dlam, qx_value, qy_value, coord, tof) # make image image = self.get_image(qx_value, qy_value, sigma_1, sigma_2, sigma_r, qx_min, qx_max, qy_min, qy_max, coord, False) if qx_min > self.qx_min: qx_min = self.qx_min if qx_max < self.qx_max: qx_max = self.qx_max if qy_min > self.qy_min: qy_min = self.qy_min if qy_max < self.qy_max: qy_max = self.qy_max # set max qranges self.qxrange = [qx_min, qx_max] self.qyrange = [qy_min, qy_max] sig1_list.append(sigma_1) sig2_list.append(sigma_2) sigr_list.append(sigma_r) sigma1d_list.append(sigma1d) # redraw image in global 2d q-space. self.image_lam = [] total_intensity = 0 sigma_1 = 0 sigma_r = 0 sigma_2 = 0 sigma1d = 0 for ind in range(num_lamda): lam = lamda_list[ind] dlam = dlamb_list[ind] intens = self.setup_tof(lam, dlam) out = self.get_image(qx_value, qy_value, sig1_list[ind], sig2_list[ind], sigr_list[ind], qx_min, qx_max, qy_min, qy_max, coord) # this is the case of q being outside the detector #if numpy.all(out==0.0): # continue image = out # set variance as sigmas sigma_1 += sig1_list[ind] * sig1_list[ind] * self.intensity sigma_r += sigr_list[ind] * sigr_list[ind] * self.intensity sigma_2 += sig2_list[ind] * sig2_list[ind] * self.intensity sigma1d += sigma1d_list[ind] * sigma1d_list[ind] * self.intensity total_intensity += self.intensity if total_intensity != 0: # average variance image_out = image / total_intensity sigma_1 = sigma_1 / total_intensity sigma_r = sigma_r / total_intensity sigma_2 = sigma_2 / total_intensity sigma1d = sigma1d / total_intensity # set sigmas self.sigma_1 = sqrt(sigma_1) self.sigma_lamd = sqrt(sigma_r) self.sigma_2 = sqrt(sigma_2) self.sigma_1d = sqrt(sigma1d) # rescale max_im_val = 1 if max_im_val > 0: image_out /= max_im_val else: image_out = image * 0.0 # Don't calculate sigmas nor set self.sigmas! sigma_1 = 0 sigma_r = 0 sigma_2 = 0 sigma1d = 0 if len(self.image) > 0: self.image += image_out else: self.image = image_out # plot image return self.plot_image(self.image) def setup_tof(self, wavelength, wavelength_spread): """ Setup all parameters in instrument : param ind: index of lambda, etc """ # set wave.wavelength self.set_wavelength(wavelength) self.set_wavelength_spread(wavelength_spread) self.intensity = self.wave.get_intensity() if wavelength == 0: msg = "Can't compute the resolution: the wavelength is zero..." raise RuntimeError, msg return self.intensity def compute(self, wavelength, wavelength_spread, qx_value, qy_value, coord='cartesian', tof=False): """ Compute the Q resoltuion in || and + direction of 2D : qx_value: x component of q : qy_value: y component of q """ coord = 'cartesian' lamb = wavelength lamb_spread = wavelength_spread # the shape of wavelength distribution if tof: # rectangular tof_factor = 2 else: # triangular tof_factor = 1 # Find polar values qr_value, phi = self._get_polar_value(qx_value, qy_value) # vacuum wave transfer knot = 2 * pi / lamb # scattering angle theta; always true for plane detector # aligned vertically to the ko direction if qr_value > knot: theta = pi / 2 else: theta = math.asin(qr_value / knot) # source aperture size rone = self.source_aperture_size # sample aperture size rtwo = self.sample_aperture_size # detector pixel size rthree = self.detector_pix_size # source to sample(aperture) distance l_ssa = self.source2sample_distance[0] # sample(aperture) to detector distance l_sad = self.sample2detector_distance[0] # sample (aperture) to sample distance l_sas = self.sample2sample_distance[0] # source to sample distance l_one = l_ssa + l_sas # sample to detector distance l_two = l_sad - l_sas # Sample offset correction for l_one and Lp on variance calculation l1_cor = (l_ssa * l_two) / (l_sas + l_two) lp_cor = (l_ssa * l_two) / (l_one + l_two) # the radial distance to the pixel from the center of the detector radius = math.tan(theta) * l_two #Lp = l_one*l_two/(l_one+l_two) # default polar coordinate comp1 = 'radial' comp2 = 'phi' # in the case of the cartesian coordinate if coord == 'cartesian': comp1 = 'x' comp2 = 'y' # sigma in the radial/x direction # for source aperture sigma_1 = self.get_variance(rone, l1_cor, phi, comp1) # for sample apperture sigma_1 += self.get_variance(rtwo, lp_cor, phi, comp1) # for detector pix sigma_1 += self.get_variance(rthree, l_two, phi, comp1) # for gravity term for 1d sigma_1grav1d = self.get_variance_gravity( l_ssa, l_sad, lamb, lamb_spread, phi, comp1, 'on') / tof_factor # for wavelength spread # reserve for 1d calculation A_value = self._cal_A_value(lamb, l_ssa, l_sad) sigma_wave_1, sigma_wave_1_1d = self.get_variance_wave( A_value, radius, l_two, lamb_spread, phi, 'radial', 'on') sigma_wave_1 /= tof_factor sigma_wave_1_1d /= tof_factor # for 1d variance_1d_1 = (sigma_1 + sigma_1grav1d) / 2 + sigma_wave_1_1d # normalize variance_1d_1 = knot * knot * variance_1d_1 / 12 # for 2d #sigma_1 += sigma_wave_1 # normalize sigma_1 = knot * sqrt(sigma_1 / 12) sigma_r = knot * sqrt(sigma_wave_1 / (tof_factor * 12)) # sigma in the phi/y direction # for source apperture sigma_2 = self.get_variance(rone, l1_cor, phi, comp2) # for sample apperture sigma_2 += self.get_variance(rtwo, lp_cor, phi, comp2) # for detector pix sigma_2 += self.get_variance(rthree, l_two, phi, comp2) # for gravity term for 1d sigma_2grav1d = self.get_variance_gravity( l_ssa, l_sad, lamb, lamb_spread, phi, comp2, 'on') / tof_factor # for wavelength spread # reserve for 1d calculation sigma_wave_2, sigma_wave_2_1d = self.get_variance_wave( A_value, radius, l_two, lamb_spread, phi, 'phi', 'on') sigma_wave_2 /= tof_factor sigma_wave_2_1d /= tof_factor # for 1d variance_1d_2 = (sigma_2 + sigma_2grav1d) / 2 + sigma_wave_2_1d # normalize variance_1d_2 = knot * knot * variance_1d_2 / 12 # for 2d #sigma_2 = knot*sqrt(sigma_2/12) #sigma_2 += sigma_wave_2 # normalize sigma_2 = knot * sqrt(sigma_2 / 12) sigma1d = sqrt(variance_1d_1 + variance_1d_2) # set sigmas self.sigma_1 = sigma_1 self.sigma_lamd = sigma_r self.sigma_2 = sigma_2 self.sigma_1d = sigma1d return qr_value, phi, sigma_1, sigma_2, sigma_r, sigma1d def _within_detector_range(self, qx_value, qy_value): """ check if qvalues are within detector range """ # detector range detector_qx_min = self.detector_qx_min detector_qx_max = self.detector_qx_max detector_qy_min = self.detector_qy_min detector_qy_max = self.detector_qy_max if self.qxmin_limit > detector_qx_min: self.qxmin_limit = detector_qx_min if self.qxmax_limit < detector_qx_max: self.qxmax_limit = detector_qx_max if self.qymin_limit > detector_qy_min: self.qymin_limit = detector_qy_min if self.qymax_limit < detector_qy_max: self.qymax_limit = detector_qy_max if qx_value < detector_qx_min or qx_value > detector_qx_max: return False if qy_value < detector_qy_min or qy_value > detector_qy_max: return False return True def get_image(self, qx_value, qy_value, sigma_1, sigma_2, sigma_r, qx_min, qx_max, qy_min, qy_max, coord='cartesian', full_cal=True): """ Get the resolution in polar coordinate ready to plot : qx_value: qx_value value : qy_value: qy_value value : sigma_1: variance in r direction : sigma_2: variance in phi direction : coord: coordinate system of image, 'polar' or 'cartesian' """ # Get qx_max and qy_max... self._get_detector_qxqy_pixels() qr_value, phi = self._get_polar_value(qx_value, qy_value) # Check whether the q value is within the detector range if qx_min < self.qx_min: self.qx_min = qx_min #raise ValueError, msg if qx_max > self.qx_max: self.qx_max = qx_max #raise ValueError, msg if qy_min < self.qy_min: self.qy_min = qy_min #raise ValueError, msg if qy_max > self.qy_max: self.qy_max = qy_max #raise ValueError, msg if not full_cal: return None # Make an empty graph in the detector scale dx_size = (self.qx_max - self.qx_min) / (1000 - 1) dy_size = (self.qy_max - self.qy_min) / (1000 - 1) x_val = numpy.arange(self.qx_min, self.qx_max, dx_size) y_val = numpy.arange(self.qy_max, self.qy_min, -dy_size) q_1, q_2 = numpy.meshgrid(x_val, y_val) #q_phi = numpy.arctan(q_1,q_2) # check whether polar or cartesian if coord == 'polar': # Find polar values qr_value, phi = self._get_polar_value(qx_value, qy_value) q_1, q_2 = self._rotate_z(q_1, q_2, phi) qc_1 = qr_value qc_2 = 0.0 # Calculate the 2D Gaussian distribution image image = self._gaussian2d_polar(q_1, q_2, qc_1, qc_2, sigma_1, sigma_2, sigma_r) else: # catesian coordinate # qx_center qc_1 = qx_value # qy_center qc_2 = qy_value # Calculate the 2D Gaussian distribution image image = self._gaussian2d(q_1, q_2, qc_1, qc_2, sigma_1, sigma_2, sigma_r) # out side of detector if not self._within_detector_range(qx_value, qy_value): image *= 0.0 self.intensity = 0.0 #return self.image # Add it if there are more than one inputs. if len(self.image_lam) > 0: self.image_lam += image * self.intensity else: self.image_lam = image * self.intensity return self.image_lam def plot_image(self, image): """ Plot image using pyplot : image: 2d resolution image : return plt: pylab object """ import matplotlib.pyplot as plt self.plot = plt plt.xlabel('$\\rm{Q}_{x} [A^{-1}]$') plt.ylabel('$\\rm{Q}_{y} [A^{-1}]$') # Max value of the image # max = numpy.max(image) qx_min, qx_max, qy_min, qy_max = self.get_detector_qrange() # Image im = plt.imshow(image, extent=[qx_min, qx_max, qy_min, qy_max]) # bilinear interpolation to make it smoother im.set_interpolation('bilinear') return plt def reset_image(self): """ Reset image to default (=[]) """ self.image = [] def get_variance(self, size=[], distance=0, phi=0, comp='radial'): """ Get the variance when the slit/pinhole size is given : size: list that can be one(diameter for circular) or two components(lengths for rectangular) : distance: [z, x] where z along the incident beam, x // qx_value : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 """ # check the length of size (list) len_size = len(size) # define sigma component direction if comp == 'radial': phi_x = math.cos(phi) phi_y = math.sin(phi) elif comp == 'phi': phi_x = math.sin(phi) phi_y = math.cos(phi) elif comp == 'x': phi_x = 1 phi_y = 0 elif comp == 'y': phi_x = 0 phi_y = 1 else: phi_x = 0 phi_y = 0 # calculate each component # for pinhole w/ radius = size[0]/2 if len_size == 1: x_comp = (0.5 * size[0]) * sqrt(3) y_comp = 0 # for rectangular slit elif len_size == 2: x_comp = size[0] * phi_x y_comp = size[1] * phi_y # otherwise else: raise ValueError, " Improper input..." # get them squared sigma = x_comp * x_comp sigma += y_comp * y_comp # normalize by distance sigma /= (distance * distance) return sigma def get_variance_wave(self, A_value, radius, distance, spread, phi, comp='radial', switch='on'): """ Get the variance when the wavelength spread is given : radius: the radial distance from the beam center to the pix of q : distance: sample to detector distance : spread: wavelength spread (ratio) : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 for 2d, sigma^2 for 1d [tuple] """ if switch.lower() == 'off': return 0, 0 # check the singular point if distance == 0 or comp == 'phi': return 0, 0 else: # calculate sigma^2 for 1d sigma1d = 2 * math.pow(radius / distance * spread, 2) if comp == 'x': sigma1d *= (math.cos(phi) * math.cos(phi)) elif comp == 'y': sigma1d *= (math.sin(phi) * math.sin(phi)) else: sigma1d *= 1 # sigma^2 for 2d # shift the coordinate due to the gravitational shift rad_x = radius * math.cos(phi) rad_y = A_value - radius * math.sin(phi) radius = math.sqrt(rad_x * rad_x + rad_y * rad_y) # new phi phi = math.atan2(-rad_y, rad_x) self.gravity_phi = phi # calculate sigma^2 sigma = 2 * math.pow(radius / distance * spread, 2) if comp == 'x': sigma *= (math.cos(phi) * math.cos(phi)) elif comp == 'y': sigma *= (math.sin(phi) * math.sin(phi)) else: sigma *= 1 return sigma, sigma1d def get_variance_gravity(self, s_distance, d_distance, wavelength, spread, phi, comp='radial', switch='on'): """ Get the variance from gravity when the wavelength spread is given : s_distance: source to sample distance : d_distance: sample to detector distance : wavelength: wavelength : spread: wavelength spread (ratio) : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 """ if switch.lower() == 'off': return 0 if self.mass == 0.0: return 0 # check the singular point if d_distance == 0 or comp == 'x': return 0 else: a_value = self._cal_A_value(None, s_distance, d_distance) # calculate sigma^2 sigma = math.pow(a_value / d_distance, 2) sigma *= math.pow(wavelength, 4) sigma *= math.pow(spread, 2) sigma *= 8 return sigma def _cal_A_value(self, lamda, s_distance, d_distance): """ Calculate A value for gravity : s_distance: source to sample distance : d_distance: sample to detector distance """ # neutron mass in cgs unit self.mass = self.get_neutron_mass() # plank constant in cgs unit h_constant = _PLANK_H # gravity in cgs unit gravy = _GRAVITY # m/h m_over_h = self.mass / h_constant # A value a_value = d_distance * (s_distance + d_distance) a_value *= math.pow(m_over_h / 2, 2) a_value *= gravy # unit correction (1/cm to 1/A) for A and d_distance below a_value *= 1.0E-16 # if lamda is give (broad meanning of A) return 2* lamda^2 * A if lamda != None: a_value *= (4 * lamda * lamda) return a_value def get_intensity(self): """ Get intensity """ return self.wave.intensity def get_wavelength(self): """ Get wavelength """ return self.wave.wavelength def get_default_spectrum(self): """ Get default_spectrum """ return self.wave.get_default_spectrum() def get_spectrum(self): """ Get _spectrum """ return self.wave.get_spectrum() def get_wavelength_spread(self): """ Get wavelength spread """ return self.wave.wavelength_spread def get_neutron_mass(self): """ Get Neutron mass """ return self.wave.mass def get_source_aperture_size(self): """ Get source aperture size """ return self.aperture.source_size def get_sample_aperture_size(self): """ Get sample aperture size """ return self.aperture.sample_size def get_detector_pix_size(self): """ Get detector pixel size """ return self.detector.pix_size def get_detector_size(self): """ Get detector size """ return self.detector.size def get_source2sample_distance(self): """ Get detector source2sample_distance """ return self.aperture.sample_distance def get_sample2sample_distance(self): """ Get detector sampleslitsample_distance """ return self.sample.distance def get_sample2detector_distance(self): """ Get detector sample2detector_distance """ return self.detector.distance def set_intensity(self, intensity): """ Set intensity """ self.wave.set_intensity(intensity) def set_wave(self, wavelength): """ Set wavelength list or wavelength """ if wavelength.__class__.__name__ == 'list': self.wave.set_wave_list(wavelength) elif wavelength.__class__.__name__ == 'float': self.wave.set_wave_list([wavelength]) #self.set_wavelength(wavelength) else: raise def set_wave_spread(self, wavelength_spread): """ Set wavelength spread or wavelength spread """ if wavelength_spread.__class__.__name__ == 'list': self.wave.set_wave_spread_list(wavelength_spread) elif wavelength_spread.__class__.__name__ == 'float': self.wave.set_wave_spread_list([wavelength_spread]) else: raise def set_wavelength(self, wavelength): """ Set wavelength """ self.wavelength = wavelength self.wave.set_wavelength(wavelength) def set_spectrum(self, spectrum): """ Set spectrum """ self.spectrum = spectrum self.wave.set_spectrum(spectrum) def set_wavelength_spread(self, wavelength_spread): """ Set wavelength spread """ self.wavelength_spread = wavelength_spread self.wave.set_wavelength_spread(wavelength_spread) def set_wave_list(self, wavelength_list, wavelengthspread_list): """ Set wavelength and its spread list """ self.wave.set_wave_list(wavelength_list) self.wave.set_wave_spread_list(wavelengthspread_list) def get_wave_list(self): """ Set wavelength spread """ return self.wave.get_wave_list() def get_intensity_list(self): """ Set wavelength spread """ return self.wave.get_intensity_list() def set_source_aperture_size(self, size): """ Set source aperture size : param size: [dia_value] or [x_value, y_value] """ if len(size) < 1 or len(size) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_source_size(size) def set_neutron_mass(self, mass): """ Set Neutron mass """ self.wave.set_mass(mass) self.mass = mass def set_sample_aperture_size(self, size): """ Set sample aperture size : param size: [dia_value] or [xheight_value, yheight_value] """ if len(size) < 1 or len(size) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_sample_size(size) def set_detector_pix_size(self, size): """ Set detector pixel size """ self.detector.set_pix_size(size) def set_detector_size(self, size): """ Set detector size in number of pixels : param size: [pixel_nums] or [x_pix_num, yx_pix_num] """ self.detector.set_size(size) def set_source2sample_distance(self, distance): """ Set detector source2sample_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_sample_distance(distance) def set_sample2sample_distance(self, distance): """ Set detector sample_slit2sample_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.sample.set_distance(distance) def set_sample2detector_distance(self, distance): """ Set detector sample2detector_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.detector.set_distance(distance) def get_all_instrument_params(self): """ Get all instrumental parameters """ self.mass = self.get_neutron_mass() self.spectrum = self.get_spectrum() self.source_aperture_size = self.get_source_aperture_size() self.sample_aperture_size = self.get_sample_aperture_size() self.detector_pix_size = self.get_detector_pix_size() self.detector_size = self.get_detector_size() self.source2sample_distance = self.get_source2sample_distance() self.sample2sample_distance = self.get_sample2sample_distance() self.sample2detector_distance = self.get_sample2detector_distance() def get_detector_qrange(self): """ get max detector q ranges : return: qx_min, qx_max, qy_min, qy_max tuple """ if len(self.qxrange) != 2 or len(self.qyrange) != 2: return None qx_min = self.qxrange[0] qx_max = self.qxrange[1] qy_min = self.qyrange[0] qy_max = self.qyrange[1] return qx_min, qx_max, qy_min, qy_max def _rotate_z(self, x_value, y_value, theta=0.0): """ Rotate x-y cordinate around z-axis by theta : x_value: numpy array of x values : y_value: numpy array of y values : theta: angle to rotate by in rad :return: x_prime, y-prime """ # rotate by theta x_prime = x_value * math.cos(theta) + y_value * math.sin(theta) y_prime = -x_value * math.sin(theta) + y_value * math.cos(theta) return x_prime, y_prime def _gaussian2d(self, x_val, y_val, x0_val, y0_val, sigma_x, sigma_y, sigma_r): """ Calculate 2D Gaussian distribution : x_val: x value : y_val: y value : x0_val: mean value in x-axis : y0_val: mean value in y-axis : sigma_x: variance in x-direction : sigma_y: variance in y-direction : return: gaussian (value) """ # phi values at each points (not at the center) x_value = x_val - x0_val y_value = y_val - y0_val phi_i = numpy.arctan2(y_val, x_val) # phi correction due to the gravity shift (in phi) phi_0 = math.atan2(y0_val, x0_val) phi_i = phi_i - phi_0 + self.gravity_phi sin_phi = numpy.sin(self.gravity_phi) cos_phi = numpy.cos(self.gravity_phi) x_p = x_value * cos_phi + y_value * sin_phi y_p = -x_value * sin_phi + y_value * cos_phi new_sig_x = sqrt(sigma_r * sigma_r / (sigma_x * sigma_x) + 1) new_sig_y = sqrt(sigma_r * sigma_r / (sigma_y * sigma_y) + 1) new_x = x_p * cos_phi / new_sig_x - y_p * sin_phi new_x /= sigma_x new_y = x_p * sin_phi / new_sig_y + y_p * cos_phi new_y /= sigma_y nu_value = -0.5 * (new_x * new_x + new_y * new_y) gaussian = numpy.exp(nu_value) # normalizing factor correction gaussian /= gaussian.sum() return gaussian def _gaussian2d_polar(self, x_val, y_val, x0_val, y0_val, sigma_x, sigma_y, sigma_r): """ Calculate 2D Gaussian distribution for polar coodinate : x_val: x value : y_val: y value : x0_val: mean value in x-axis : y0_val: mean value in y-axis : sigma_x: variance in r-direction : sigma_y: variance in phi-direction : sigma_r: wavelength variance in r-direction : return: gaussian (value) """ sigma_x = sqrt(sigma_x * sigma_x + sigma_r * sigma_r) # call gaussian1d gaussian = self._gaussian1d(x_val, x0_val, sigma_x) gaussian *= self._gaussian1d(y_val, y0_val, sigma_y) # normalizing factor correction if sigma_x != 0 and sigma_y != 0: gaussian *= sqrt(2 * pi) return gaussian def _gaussian1d(self, value, mean, sigma): """ Calculate 1D Gaussian distribution : value: value : mean: mean value : sigma: variance : return: gaussian (value) """ # default gaussian = 1.0 if sigma != 0: # get exponent nu_value = (value - mean) / sigma nu_value *= nu_value nu_value *= -0.5 gaussian *= numpy.exp(nu_value) gaussian /= sigma # normalize gaussian /= sqrt(2 * pi) return gaussian def _atan_phi(self, qy_value, qx_value): """ Find the angle phi of q on the detector plane for qx_value, qy_value given : qx_value: x component of q : qy_value: y component of q : return phi: the azimuthal angle of q on x-y plane """ phi = math.atan2(qy_value, qx_value) return phi def _get_detector_qxqy_pixels(self): """ Get the pixel positions of the detector in the qx_value-qy_value space """ # update all param values self.get_all_instrument_params() # wavelength wavelength = self.wave.wavelength # Gavity correction delta_y = self._get_beamcenter_drop() # in cm # detector_pix size detector_pix_size = self.detector_pix_size # Square or circular pixel if len(detector_pix_size) == 1: pix_x_size = detector_pix_size[0] pix_y_size = detector_pix_size[0] # rectangular pixel pixel elif len(detector_pix_size) == 2: pix_x_size = detector_pix_size[0] pix_y_size = detector_pix_size[1] else: raise ValueError, " Input value format error..." # Sample to detector distance = sample slit to detector # minus sample offset sample2detector_distance = self.sample2detector_distance[0] - \ self.sample2sample_distance[0] # detector offset in x-direction detector_offset = 0 try: detector_offset = self.sample2detector_distance[1] except: logging.error(sys.exc_value) # detector size in [no of pix_x,no of pix_y] detector_pix_nums_x = self.detector_size[0] # get pix_y if it exists, otherwse take it from [0] try: detector_pix_nums_y = self.detector_size[1] except: detector_pix_nums_y = self.detector_size[0] # detector offset in pix number offset_x = detector_offset / pix_x_size offset_y = delta_y / pix_y_size # beam center position in pix number (start from 0) center_x, center_y = self._get_beamcenter_position( detector_pix_nums_x, detector_pix_nums_y, offset_x, offset_y) # distance [cm] from the beam center on detector plane detector_ind_x = numpy.arange(detector_pix_nums_x) detector_ind_y = numpy.arange(detector_pix_nums_y) # shif 0.5 pixel so that pix position is at the center of the pixel detector_ind_x = detector_ind_x + 0.5 detector_ind_y = detector_ind_y + 0.5 # the relative postion from the beam center detector_ind_x = detector_ind_x - center_x detector_ind_y = detector_ind_y - center_y # unit correction in cm detector_ind_x = detector_ind_x * pix_x_size detector_ind_y = detector_ind_y * pix_y_size qx_value = numpy.zeros(len(detector_ind_x)) qy_value = numpy.zeros(len(detector_ind_y)) i = 0 for indx in detector_ind_x: qx_value[i] = self._get_qx(indx, sample2detector_distance, wavelength) i += 1 i = 0 for indy in detector_ind_y: qy_value[i] = self._get_qx(indy, sample2detector_distance, wavelength) i += 1 # qx_value and qy_value values in array qx_value = qx_value.repeat(detector_pix_nums_y) qx_value = qx_value.reshape(detector_pix_nums_x, detector_pix_nums_y) qy_value = qy_value.repeat(detector_pix_nums_x) qy_value = qy_value.reshape(detector_pix_nums_y, detector_pix_nums_x) qy_value = qy_value.transpose() # p min and max values among the center of pixels self.qx_min = numpy.min(qx_value) self.qx_max = numpy.max(qx_value) self.qy_min = numpy.min(qy_value) self.qy_max = numpy.max(qy_value) # Appr. min and max values of the detector display limits # i.e., edges of the last pixels. self.qy_min += self._get_qx(-0.5 * pix_y_size, sample2detector_distance, wavelength) self.qy_max += self._get_qx(0.5 * pix_y_size, sample2detector_distance, wavelength) #if self.qx_min == self.qx_max: self.qx_min += self._get_qx(-0.5 * pix_x_size, sample2detector_distance, wavelength) self.qx_max += self._get_qx(0.5 * pix_x_size, sample2detector_distance, wavelength) # min and max values of detecter self.detector_qx_min = self.qx_min self.detector_qx_max = self.qx_max self.detector_qy_min = self.qy_min self.detector_qy_max = self.qy_max # try to set it as a Data2D otherwise pass (not required for now) try: from sas.sascalc.dataloader.data_info import Data2D output = Data2D() inten = numpy.zeros_like(qx_value) output.data = inten output.qx_data = qx_value output.qy_data = qy_value except: logging.error(sys.exc_value) return output def _get_qx(self, dx_size, det_dist, wavelength): """ :param dx_size: x-distance from beam center [cm] :param det_dist: sample to detector distance [cm] :return: q-value at the given position """ # Distance from beam center in the plane of detector plane_dist = dx_size # full scattering angle on the x-axis theta = numpy.arctan(plane_dist / det_dist) qx_value = (2.0 * pi / wavelength) * numpy.sin(theta) return qx_value def _get_polar_value(self, qx_value, qy_value): """ Find qr_value and phi from qx_value and qy_value values : return qr_value, phi """ # find |q| on detector plane qr_value = sqrt(qx_value * qx_value + qy_value * qy_value) # find angle phi phi = self._atan_phi(qy_value, qx_value) return qr_value, phi def _get_beamcenter_position(self, num_x, num_y, offset_x, offset_y): """ :param num_x: number of pixel in x-direction :param num_y: number of pixel in y-direction :param offset: detector offset in x-direction in pix number :return: pix number; pos_x, pos_y in pix index """ # beam center position pos_x = num_x / 2 pos_y = num_y / 2 # correction for offset pos_x += offset_x # correction for gravity that is always negative pos_y -= offset_y return pos_x, pos_y def _get_beamcenter_drop(self): """ Get the beam center drop (delta y) in y diection due to gravity :return delta y: the beam center drop in cm """ # Check if mass == 0 (X-ray). if self.mass == 0: return 0 # Covert unit from A to cm unit_cm = 1e-08 # Velocity of neutron in horizontal direction (~ actual velocity) velocity = _PLANK_H / (self.mass * self.wave.wavelength * unit_cm) # Compute delta y delta_y = 0.5 delta_y *= _GRAVITY sampletodetector = self.sample2detector_distance[0] - \ self.sample2sample_distance[0] delta_y *= sampletodetector delta_y *= (self.source2sample_distance[0] + self.sample2detector_distance[0]) delta_y /= (velocity * velocity) return delta_y
class ResolutionCalculator(object): """ compute resolution in 2D """ def __init__(self): # wavelength self.wave = Neutron() # sample self.sample = Sample() # aperture self.aperture = Aperture() # detector self.detector = Detector() # 2d image of the resolution self.image = [] self.image_lam = [] # resolutions # lamda in r-direction self.sigma_lamd = 0 # x-dir (no lamda) self.sigma_1 = 0 #y-dir (no lamda) self.sigma_2 = 0 # 1D total self.sigma_1d = 0 self.gravity_phi = None # q min and max self.qx_min = -0.3 self.qx_max = 0.3 self.qy_min = -0.3 self.qy_max = 0.3 # q min and max of the detector self.detector_qx_min = -0.3 self.detector_qx_max = 0.3 self.detector_qy_min = -0.3 self.detector_qy_max = 0.3 # possible max qrange self.qxmin_limit = 0 self.qxmax_limit = 0 self.qymin_limit = 0 self.qymax_limit = 0 # plots self.plot = None # instrumental params defaults self.mass = 0 self.intensity = 0 self.wavelength = 0 self.wavelength_spread = 0 self.source_aperture_size = [] self.source2sample_distance = [] self.sample2sample_distance = [] self.sample_aperture_size = [] self.sample2detector_distance = [] self.detector_pix_size = [] self.detector_size = [] self.get_all_instrument_params() # max q range for all lambdas self.qxrange = [] self.qyrange = [] def compute_and_plot(self, qx_value, qy_value, qx_min, qx_max, qy_min, qy_max, coord='cartesian'): """ Compute the resolution : qx_value: x component of q : qy_value: y component of q """ # make sure to update all the variables need. # except lambda, dlambda, and intensity self.get_all_instrument_params() # wavelength etc. lamda_list, dlamb_list = self.get_wave_list() intens_list = [] sig1_list = [] sig2_list = [] sigr_list = [] sigma1d_list = [] num_lamda = len(lamda_list) for num in range(num_lamda): lam = lamda_list[num] # wavelength spread dlam = dlamb_list[num] intens = self.setup_tof(lam, dlam) intens_list.append(intens) # cehck if tof if num_lamda > 1: tof = True else: tof = False # compute 2d resolution _, _, sigma_1, sigma_2, sigma_r, sigma1d = \ self.compute(lam, dlam, qx_value, qy_value, coord, tof) # make image image = self.get_image(qx_value, qy_value, sigma_1, sigma_2, sigma_r, qx_min, qx_max, qy_min, qy_max, coord, False) if qx_min > self.qx_min: qx_min = self.qx_min if qx_max < self.qx_max: qx_max = self.qx_max if qy_min > self.qy_min: qy_min = self.qy_min if qy_max < self.qy_max: qy_max = self.qy_max # set max qranges self.qxrange = [qx_min, qx_max] self.qyrange = [qy_min, qy_max] sig1_list.append(sigma_1) sig2_list.append(sigma_2) sigr_list.append(sigma_r) sigma1d_list.append(sigma1d) # redraw image in global 2d q-space. self.image_lam = [] total_intensity = 0 sigma_1 = 0 sigma_r = 0 sigma_2 = 0 sigma1d = 0 for ind in range(num_lamda): lam = lamda_list[ind] dlam = dlamb_list[ind] intens = self.setup_tof(lam, dlam) out = self.get_image(qx_value, qy_value, sig1_list[ind], sig2_list[ind], sigr_list[ind], qx_min, qx_max, qy_min, qy_max, coord) # this is the case of q being outside the detector #if numpy.all(out==0.0): # continue image = out # set variance as sigmas sigma_1 += sig1_list[ind] * sig1_list[ind] * self.intensity sigma_r += sigr_list[ind] * sigr_list[ind] * self.intensity sigma_2 += sig2_list[ind] * sig2_list[ind] * self.intensity sigma1d += sigma1d_list[ind] * sigma1d_list[ind] * self.intensity total_intensity += self.intensity if total_intensity != 0: # average variance image_out = image / total_intensity sigma_1 = sigma_1 / total_intensity sigma_r = sigma_r / total_intensity sigma_2 = sigma_2 / total_intensity sigma1d = sigma1d / total_intensity # set sigmas self.sigma_1 = sqrt(sigma_1) self.sigma_lamd = sqrt(sigma_r) self.sigma_2 = sqrt(sigma_2) self.sigma_1d = sqrt(sigma1d) # rescale max_im_val = 1 if max_im_val > 0: image_out /= max_im_val else: image_out = image * 0.0 # Don't calculate sigmas nor set self.sigmas! sigma_1 = 0 sigma_r = 0 sigma_2 = 0 sigma1d = 0 if len(self.image) > 0: self.image += image_out else: self.image = image_out # plot image return self.plot_image(self.image) def setup_tof(self, wavelength, wavelength_spread): """ Setup all parameters in instrument : param ind: index of lambda, etc """ # set wave.wavelength self.set_wavelength(wavelength) self.set_wavelength_spread(wavelength_spread) self.intensity = self.wave.get_intensity() if wavelength == 0: msg = "Can't compute the resolution: the wavelength is zero..." raise RuntimeError, msg return self.intensity def compute(self, wavelength, wavelength_spread, qx_value, qy_value, coord='cartesian', tof=False): """ Compute the Q resoltuion in || and + direction of 2D : qx_value: x component of q : qy_value: y component of q """ coord = 'cartesian' lamb = wavelength lamb_spread = wavelength_spread # the shape of wavelength distribution if tof: # rectangular tof_factor = 2 else: # triangular tof_factor = 1 # Find polar values qr_value, phi = self._get_polar_value(qx_value, qy_value) # vacuum wave transfer knot = 2*pi/lamb # scattering angle theta; always true for plane detector # aligned vertically to the ko direction if qr_value > knot: theta = pi/2 else: theta = math.asin(qr_value/knot) # source aperture size rone = self.source_aperture_size # sample aperture size rtwo = self.sample_aperture_size # detector pixel size rthree = self.detector_pix_size # source to sample(aperture) distance l_ssa = self.source2sample_distance[0] # sample(aperture) to detector distance l_sad = self.sample2detector_distance[0] # sample (aperture) to sample distance l_sas = self.sample2sample_distance[0] # source to sample distance l_one = l_ssa + l_sas # sample to detector distance l_two = l_sad - l_sas # Sample offset correction for l_one and Lp on variance calculation l1_cor = (l_ssa * l_two) / (l_sas + l_two) lp_cor = (l_ssa * l_two) / (l_one + l_two) # the radial distance to the pixel from the center of the detector radius = math.tan(theta) * l_two #Lp = l_one*l_two/(l_one+l_two) # default polar coordinate comp1 = 'radial' comp2 = 'phi' # in the case of the cartesian coordinate if coord == 'cartesian': comp1 = 'x' comp2 = 'y' # sigma in the radial/x direction # for source aperture sigma_1 = self.get_variance(rone, l1_cor, phi, comp1) # for sample apperture sigma_1 += self.get_variance(rtwo, lp_cor, phi, comp1) # for detector pix sigma_1 += self.get_variance(rthree, l_two, phi, comp1) # for gravity term for 1d sigma_1grav1d = self.get_variance_gravity(l_ssa, l_sad, lamb, lamb_spread, phi, comp1, 'on') / tof_factor # for wavelength spread # reserve for 1d calculation A_value = self._cal_A_value(lamb, l_ssa, l_sad) sigma_wave_1, sigma_wave_1_1d = self.get_variance_wave(A_value, radius, l_two, lamb_spread, phi, 'radial', 'on') sigma_wave_1 /= tof_factor sigma_wave_1_1d /= tof_factor # for 1d variance_1d_1 = (sigma_1 + sigma_1grav1d) / 2 + sigma_wave_1_1d # normalize variance_1d_1 = knot * knot * variance_1d_1 / 12 # for 2d #sigma_1 += sigma_wave_1 # normalize sigma_1 = knot * sqrt(sigma_1 / 12) sigma_r = knot * sqrt(sigma_wave_1 / (tof_factor *12)) # sigma in the phi/y direction # for source apperture sigma_2 = self.get_variance(rone, l1_cor, phi, comp2) # for sample apperture sigma_2 += self.get_variance(rtwo, lp_cor, phi, comp2) # for detector pix sigma_2 += self.get_variance(rthree, l_two, phi, comp2) # for gravity term for 1d sigma_2grav1d = self.get_variance_gravity(l_ssa, l_sad, lamb, lamb_spread, phi, comp2, 'on') / tof_factor # for wavelength spread # reserve for 1d calculation sigma_wave_2, sigma_wave_2_1d = self.get_variance_wave(A_value, radius, l_two, lamb_spread, phi, 'phi', 'on') sigma_wave_2 /= tof_factor sigma_wave_2_1d /= tof_factor # for 1d variance_1d_2 = (sigma_2 + sigma_2grav1d) / 2 + sigma_wave_2_1d # normalize variance_1d_2 = knot * knot * variance_1d_2 / 12 # for 2d #sigma_2 = knot*sqrt(sigma_2/12) #sigma_2 += sigma_wave_2 # normalize sigma_2 = knot * sqrt(sigma_2 / 12) sigma1d = sqrt(variance_1d_1 + variance_1d_2) # set sigmas self.sigma_1 = sigma_1 self.sigma_lamd = sigma_r self.sigma_2 = sigma_2 self.sigma_1d = sigma1d return qr_value, phi, sigma_1, sigma_2, sigma_r, sigma1d def _within_detector_range(self, qx_value, qy_value): """ check if qvalues are within detector range """ # detector range detector_qx_min = self.detector_qx_min detector_qx_max = self.detector_qx_max detector_qy_min = self.detector_qy_min detector_qy_max = self.detector_qy_max if self.qxmin_limit > detector_qx_min: self.qxmin_limit = detector_qx_min if self.qxmax_limit < detector_qx_max: self.qxmax_limit = detector_qx_max if self.qymin_limit > detector_qy_min: self.qymin_limit = detector_qy_min if self.qymax_limit < detector_qy_max: self.qymax_limit = detector_qy_max if qx_value < detector_qx_min or qx_value > detector_qx_max: return False if qy_value < detector_qy_min or qy_value > detector_qy_max: return False return True def get_image(self, qx_value, qy_value, sigma_1, sigma_2, sigma_r, qx_min, qx_max, qy_min, qy_max, coord='cartesian', full_cal=True): """ Get the resolution in polar coordinate ready to plot : qx_value: qx_value value : qy_value: qy_value value : sigma_1: variance in r direction : sigma_2: variance in phi direction : coord: coordinate system of image, 'polar' or 'cartesian' """ # Get qx_max and qy_max... self._get_detector_qxqy_pixels() qr_value, phi = self._get_polar_value(qx_value, qy_value) # Check whether the q value is within the detector range if qx_min < self.qx_min: self.qx_min = qx_min #raise ValueError, msg if qx_max > self.qx_max: self.qx_max = qx_max #raise ValueError, msg if qy_min < self.qy_min: self.qy_min = qy_min #raise ValueError, msg if qy_max > self.qy_max: self.qy_max = qy_max #raise ValueError, msg if not full_cal: return None # Make an empty graph in the detector scale dx_size = (self.qx_max - self.qx_min) / (1000 - 1) dy_size = (self.qy_max - self.qy_min) / (1000 - 1) x_val = np.arange(self.qx_min, self.qx_max, dx_size) y_val = np.arange(self.qy_max, self.qy_min, -dy_size) q_1, q_2 = np.meshgrid(x_val, y_val) #q_phi = numpy.arctan(q_1,q_2) # check whether polar or cartesian if coord == 'polar': # Find polar values qr_value, phi = self._get_polar_value(qx_value, qy_value) q_1, q_2 = self._rotate_z(q_1, q_2, phi) qc_1 = qr_value qc_2 = 0.0 # Calculate the 2D Gaussian distribution image image = self._gaussian2d_polar(q_1, q_2, qc_1, qc_2, sigma_1, sigma_2, sigma_r) else: # catesian coordinate # qx_center qc_1 = qx_value # qy_center qc_2 = qy_value # Calculate the 2D Gaussian distribution image image = self._gaussian2d(q_1, q_2, qc_1, qc_2, sigma_1, sigma_2, sigma_r) # out side of detector if not self._within_detector_range(qx_value, qy_value): image *= 0.0 self.intensity = 0.0 #return self.image # Add it if there are more than one inputs. if len(self.image_lam) > 0: self.image_lam += image * self.intensity else: self.image_lam = image * self.intensity return self.image_lam def plot_image(self, image): """ Plot image using pyplot : image: 2d resolution image : return plt: pylab object """ import matplotlib.pyplot as plt self.plot = plt plt.xlabel('$\\rm{Q}_{x} [A^{-1}]$') plt.ylabel('$\\rm{Q}_{y} [A^{-1}]$') # Max value of the image # max = numpy.max(image) qx_min, qx_max, qy_min, qy_max = self.get_detector_qrange() # Image im = plt.imshow(image, extent=[qx_min, qx_max, qy_min, qy_max]) # bilinear interpolation to make it smoother im.set_interpolation('bilinear') return plt def reset_image(self): """ Reset image to default (=[]) """ self.image = [] def get_variance(self, size=[], distance=0, phi=0, comp='radial'): """ Get the variance when the slit/pinhole size is given : size: list that can be one(diameter for circular) or two components(lengths for rectangular) : distance: [z, x] where z along the incident beam, x // qx_value : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 """ # check the length of size (list) len_size = len(size) # define sigma component direction if comp == 'radial': phi_x = math.cos(phi) phi_y = math.sin(phi) elif comp == 'phi': phi_x = math.sin(phi) phi_y = math.cos(phi) elif comp == 'x': phi_x = 1 phi_y = 0 elif comp == 'y': phi_x = 0 phi_y = 1 else: phi_x = 0 phi_y = 0 # calculate each component # for pinhole w/ radius = size[0]/2 if len_size == 1: x_comp = (0.5 * size[0]) * sqrt(3) y_comp = 0 # for rectangular slit elif len_size == 2: x_comp = size[0] * phi_x y_comp = size[1] * phi_y # otherwise else: raise ValueError, " Improper input..." # get them squared sigma = x_comp * x_comp sigma += y_comp * y_comp # normalize by distance sigma /= (distance * distance) return sigma def get_variance_wave(self, A_value, radius, distance, spread, phi, comp='radial', switch='on'): """ Get the variance when the wavelength spread is given : radius: the radial distance from the beam center to the pix of q : distance: sample to detector distance : spread: wavelength spread (ratio) : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 for 2d, sigma^2 for 1d [tuple] """ if switch.lower() == 'off': return 0, 0 # check the singular point if distance == 0 or comp == 'phi': return 0, 0 else: # calculate sigma^2 for 1d sigma1d = 2 * math.pow(radius/distance*spread, 2) if comp == 'x': sigma1d *= (math.cos(phi)*math.cos(phi)) elif comp == 'y': sigma1d *= (math.sin(phi)*math.sin(phi)) else: sigma1d *= 1 # sigma^2 for 2d # shift the coordinate due to the gravitational shift rad_x = radius * math.cos(phi) rad_y = A_value - radius * math.sin(phi) radius = math.sqrt(rad_x * rad_x + rad_y * rad_y) # new phi phi = math.atan2(-rad_y, rad_x) self.gravity_phi = phi # calculate sigma^2 sigma = 2 * math.pow(radius/distance*spread, 2) if comp == 'x': sigma *= (math.cos(phi)*math.cos(phi)) elif comp == 'y': sigma *= (math.sin(phi)*math.sin(phi)) else: sigma *= 1 return sigma, sigma1d def get_variance_gravity(self, s_distance, d_distance, wavelength, spread, phi, comp='radial', switch='on'): """ Get the variance from gravity when the wavelength spread is given : s_distance: source to sample distance : d_distance: sample to detector distance : wavelength: wavelength : spread: wavelength spread (ratio) : comp: direction of the sigma; can be 'phi', 'y', 'x', and 'radial' : return variance: sigma^2 """ if switch.lower() == 'off': return 0 if self.mass == 0.0: return 0 # check the singular point if d_distance == 0 or comp == 'x': return 0 else: a_value = self._cal_A_value(None, s_distance, d_distance) # calculate sigma^2 sigma = math.pow(a_value / d_distance, 2) sigma *= math.pow(wavelength, 4) sigma *= math.pow(spread, 2) sigma *= 8 return sigma def _cal_A_value(self, lamda, s_distance, d_distance): """ Calculate A value for gravity : s_distance: source to sample distance : d_distance: sample to detector distance """ # neutron mass in cgs unit self.mass = self.get_neutron_mass() # plank constant in cgs unit h_constant = _PLANK_H # gravity in cgs unit gravy = _GRAVITY # m/h m_over_h = self.mass / h_constant # A value a_value = d_distance * (s_distance + d_distance) a_value *= math.pow(m_over_h / 2, 2) a_value *= gravy # unit correction (1/cm to 1/A) for A and d_distance below a_value *= 1.0E-16 # if lamda is give (broad meanning of A) return 2* lamda^2 * A if lamda is not None: a_value *= (4 * lamda * lamda) return a_value def get_intensity(self): """ Get intensity """ return self.wave.intensity def get_wavelength(self): """ Get wavelength """ return self.wave.wavelength def get_default_spectrum(self): """ Get default_spectrum """ return self.wave.get_default_spectrum() def get_spectrum(self): """ Get _spectrum """ return self.wave.get_spectrum() def get_wavelength_spread(self): """ Get wavelength spread """ return self.wave.wavelength_spread def get_neutron_mass(self): """ Get Neutron mass """ return self.wave.mass def get_source_aperture_size(self): """ Get source aperture size """ return self.aperture.source_size def get_sample_aperture_size(self): """ Get sample aperture size """ return self.aperture.sample_size def get_detector_pix_size(self): """ Get detector pixel size """ return self.detector.pix_size def get_detector_size(self): """ Get detector size """ return self.detector.size def get_source2sample_distance(self): """ Get detector source2sample_distance """ return self.aperture.sample_distance def get_sample2sample_distance(self): """ Get detector sampleslitsample_distance """ return self.sample.distance def get_sample2detector_distance(self): """ Get detector sample2detector_distance """ return self.detector.distance def set_intensity(self, intensity): """ Set intensity """ self.wave.set_intensity(intensity) def set_wave(self, wavelength): """ Set wavelength list or wavelength """ if wavelength.__class__.__name__ == 'list': self.wave.set_wave_list(wavelength) elif wavelength.__class__.__name__ == 'float': self.wave.set_wave_list([wavelength]) #self.set_wavelength(wavelength) else: raise def set_wave_spread(self, wavelength_spread): """ Set wavelength spread or wavelength spread """ if wavelength_spread.__class__.__name__ == 'list': self.wave.set_wave_spread_list(wavelength_spread) elif wavelength_spread.__class__.__name__ == 'float': self.wave.set_wave_spread_list([wavelength_spread]) else: raise def set_wavelength(self, wavelength): """ Set wavelength """ self.wavelength = wavelength self.wave.set_wavelength(wavelength) def set_spectrum(self, spectrum): """ Set spectrum """ self.spectrum = spectrum self.wave.set_spectrum(spectrum) def set_wavelength_spread(self, wavelength_spread): """ Set wavelength spread """ self.wavelength_spread = wavelength_spread self.wave.set_wavelength_spread(wavelength_spread) def set_wave_list(self, wavelength_list, wavelengthspread_list): """ Set wavelength and its spread list """ self.wave.set_wave_list(wavelength_list) self.wave.set_wave_spread_list(wavelengthspread_list) def get_wave_list(self): """ Set wavelength spread """ return self.wave.get_wave_list() def get_intensity_list(self): """ Set wavelength spread """ return self.wave.get_intensity_list() def set_source_aperture_size(self, size): """ Set source aperture size : param size: [dia_value] or [x_value, y_value] """ if len(size) < 1 or len(size) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_source_size(size) def set_neutron_mass(self, mass): """ Set Neutron mass """ self.wave.set_mass(mass) self.mass = mass def set_sample_aperture_size(self, size): """ Set sample aperture size : param size: [dia_value] or [xheight_value, yheight_value] """ if len(size) < 1 or len(size) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_sample_size(size) def set_detector_pix_size(self, size): """ Set detector pixel size """ self.detector.set_pix_size(size) def set_detector_size(self, size): """ Set detector size in number of pixels : param size: [pixel_nums] or [x_pix_num, yx_pix_num] """ self.detector.set_size(size) def set_source2sample_distance(self, distance): """ Set detector source2sample_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.aperture.set_sample_distance(distance) def set_sample2sample_distance(self, distance): """ Set detector sample_slit2sample_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.sample.set_distance(distance) def set_sample2detector_distance(self, distance): """ Set detector sample2detector_distance : param distance: [distance, x_offset] """ if len(distance) < 1 or len(distance) > 2: raise RuntimeError, "The length of the size must be one or two." self.detector.set_distance(distance) def get_all_instrument_params(self): """ Get all instrumental parameters """ self.mass = self.get_neutron_mass() self.spectrum = self.get_spectrum() self.source_aperture_size = self.get_source_aperture_size() self.sample_aperture_size = self.get_sample_aperture_size() self.detector_pix_size = self.get_detector_pix_size() self.detector_size = self.get_detector_size() self.source2sample_distance = self.get_source2sample_distance() self.sample2sample_distance = self.get_sample2sample_distance() self.sample2detector_distance = self.get_sample2detector_distance() def get_detector_qrange(self): """ get max detector q ranges : return: qx_min, qx_max, qy_min, qy_max tuple """ if len(self.qxrange) != 2 or len(self.qyrange) != 2: return None qx_min = self.qxrange[0] qx_max = self.qxrange[1] qy_min = self.qyrange[0] qy_max = self.qyrange[1] return qx_min, qx_max, qy_min, qy_max def _rotate_z(self, x_value, y_value, theta=0.0): """ Rotate x-y cordinate around z-axis by theta : x_value: numpy array of x values : y_value: numpy array of y values : theta: angle to rotate by in rad :return: x_prime, y-prime """ # rotate by theta x_prime = x_value * math.cos(theta) + y_value * math.sin(theta) y_prime = -x_value * math.sin(theta) + y_value * math.cos(theta) return x_prime, y_prime def _gaussian2d(self, x_val, y_val, x0_val, y0_val, sigma_x, sigma_y, sigma_r): """ Calculate 2D Gaussian distribution : x_val: x value : y_val: y value : x0_val: mean value in x-axis : y0_val: mean value in y-axis : sigma_x: variance in x-direction : sigma_y: variance in y-direction : return: gaussian (value) """ # phi values at each points (not at the center) x_value = x_val - x0_val y_value = y_val - y0_val phi_i = np.arctan2(y_val, x_val) # phi correction due to the gravity shift (in phi) phi_0 = math.atan2(y0_val, x0_val) phi_i = phi_i - phi_0 + self.gravity_phi sin_phi = np.sin(self.gravity_phi) cos_phi = np.cos(self.gravity_phi) x_p = x_value * cos_phi + y_value * sin_phi y_p = -x_value * sin_phi + y_value * cos_phi new_sig_x = sqrt(sigma_r * sigma_r / (sigma_x * sigma_x) + 1) new_sig_y = sqrt(sigma_r * sigma_r / (sigma_y * sigma_y) + 1) new_x = x_p * cos_phi / new_sig_x - y_p * sin_phi new_x /= sigma_x new_y = x_p * sin_phi / new_sig_y + y_p * cos_phi new_y /= sigma_y nu_value = -0.5 * (new_x * new_x + new_y * new_y) gaussian = np.exp(nu_value) # normalizing factor correction gaussian /= gaussian.sum() return gaussian def _gaussian2d_polar(self, x_val, y_val, x0_val, y0_val, sigma_x, sigma_y, sigma_r): """ Calculate 2D Gaussian distribution for polar coodinate : x_val: x value : y_val: y value : x0_val: mean value in x-axis : y0_val: mean value in y-axis : sigma_x: variance in r-direction : sigma_y: variance in phi-direction : sigma_r: wavelength variance in r-direction : return: gaussian (value) """ sigma_x = sqrt(sigma_x * sigma_x + sigma_r * sigma_r) # call gaussian1d gaussian = self._gaussian1d(x_val, x0_val, sigma_x) gaussian *= self._gaussian1d(y_val, y0_val, sigma_y) # normalizing factor correction if sigma_x != 0 and sigma_y != 0: gaussian *= sqrt(2 * pi) return gaussian def _gaussian1d(self, value, mean, sigma): """ Calculate 1D Gaussian distribution : value: value : mean: mean value : sigma: variance : return: gaussian (value) """ # default gaussian = 1.0 if sigma != 0: # get exponent nu_value = (value - mean) / sigma nu_value *= nu_value nu_value *= -0.5 gaussian *= np.exp(nu_value) gaussian /= sigma # normalize gaussian /= sqrt(2 * pi) return gaussian def _atan_phi(self, qy_value, qx_value): """ Find the angle phi of q on the detector plane for qx_value, qy_value given : qx_value: x component of q : qy_value: y component of q : return phi: the azimuthal angle of q on x-y plane """ phi = math.atan2(qy_value, qx_value) return phi def _get_detector_qxqy_pixels(self): """ Get the pixel positions of the detector in the qx_value-qy_value space """ # update all param values self.get_all_instrument_params() # wavelength wavelength = self.wave.wavelength # Gavity correction delta_y = self._get_beamcenter_drop() # in cm # detector_pix size detector_pix_size = self.detector_pix_size # Square or circular pixel if len(detector_pix_size) == 1: pix_x_size = detector_pix_size[0] pix_y_size = detector_pix_size[0] # rectangular pixel pixel elif len(detector_pix_size) == 2: pix_x_size = detector_pix_size[0] pix_y_size = detector_pix_size[1] else: raise ValueError, " Input value format error..." # Sample to detector distance = sample slit to detector # minus sample offset sample2detector_distance = self.sample2detector_distance[0] - \ self.sample2sample_distance[0] # detector offset in x-direction detector_offset = 0 try: detector_offset = self.sample2detector_distance[1] except: logger.error(sys.exc_value) # detector size in [no of pix_x,no of pix_y] detector_pix_nums_x = self.detector_size[0] # get pix_y if it exists, otherwse take it from [0] try: detector_pix_nums_y = self.detector_size[1] except: detector_pix_nums_y = self.detector_size[0] # detector offset in pix number offset_x = detector_offset / pix_x_size offset_y = delta_y / pix_y_size # beam center position in pix number (start from 0) center_x, center_y = self._get_beamcenter_position(detector_pix_nums_x, detector_pix_nums_y, offset_x, offset_y) # distance [cm] from the beam center on detector plane detector_ind_x = np.arange(detector_pix_nums_x) detector_ind_y = np.arange(detector_pix_nums_y) # shif 0.5 pixel so that pix position is at the center of the pixel detector_ind_x = detector_ind_x + 0.5 detector_ind_y = detector_ind_y + 0.5 # the relative postion from the beam center detector_ind_x = detector_ind_x - center_x detector_ind_y = detector_ind_y - center_y # unit correction in cm detector_ind_x = detector_ind_x * pix_x_size detector_ind_y = detector_ind_y * pix_y_size qx_value = np.zeros(len(detector_ind_x)) qy_value = np.zeros(len(detector_ind_y)) i = 0 for indx in detector_ind_x: qx_value[i] = self._get_qx(indx, sample2detector_distance, wavelength) i += 1 i = 0 for indy in detector_ind_y: qy_value[i] = self._get_qx(indy, sample2detector_distance, wavelength) i += 1 # qx_value and qy_value values in array qx_value = qx_value.repeat(detector_pix_nums_y) qx_value = qx_value.reshape(detector_pix_nums_x, detector_pix_nums_y) qy_value = qy_value.repeat(detector_pix_nums_x) qy_value = qy_value.reshape(detector_pix_nums_y, detector_pix_nums_x) qy_value = qy_value.transpose() # p min and max values among the center of pixels self.qx_min = np.min(qx_value) self.qx_max = np.max(qx_value) self.qy_min = np.min(qy_value) self.qy_max = np.max(qy_value) # Appr. min and max values of the detector display limits # i.e., edges of the last pixels. self.qy_min += self._get_qx(-0.5 * pix_y_size, sample2detector_distance, wavelength) self.qy_max += self._get_qx(0.5 * pix_y_size, sample2detector_distance, wavelength) #if self.qx_min == self.qx_max: self.qx_min += self._get_qx(-0.5 * pix_x_size, sample2detector_distance, wavelength) self.qx_max += self._get_qx(0.5 * pix_x_size, sample2detector_distance, wavelength) # min and max values of detecter self.detector_qx_min = self.qx_min self.detector_qx_max = self.qx_max self.detector_qy_min = self.qy_min self.detector_qy_max = self.qy_max # try to set it as a Data2D otherwise pass (not required for now) try: from sas.sascalc.dataloader.data_info import Data2D output = Data2D() inten = np.zeros_like(qx_value) output.data = inten output.qx_data = qx_value output.qy_data = qy_value except: logger.error(sys.exc_value) return output def _get_qx(self, dx_size, det_dist, wavelength): """ :param dx_size: x-distance from beam center [cm] :param det_dist: sample to detector distance [cm] :return: q-value at the given position """ # Distance from beam center in the plane of detector plane_dist = dx_size # full scattering angle on the x-axis theta = np.arctan(plane_dist / det_dist) qx_value = (2.0 * pi / wavelength) * np.sin(theta) return qx_value def _get_polar_value(self, qx_value, qy_value): """ Find qr_value and phi from qx_value and qy_value values : return qr_value, phi """ # find |q| on detector plane qr_value = sqrt(qx_value*qx_value + qy_value*qy_value) # find angle phi phi = self._atan_phi(qy_value, qx_value) return qr_value, phi def _get_beamcenter_position(self, num_x, num_y, offset_x, offset_y): """ :param num_x: number of pixel in x-direction :param num_y: number of pixel in y-direction :param offset: detector offset in x-direction in pix number :return: pix number; pos_x, pos_y in pix index """ # beam center position pos_x = num_x / 2 pos_y = num_y / 2 # correction for offset pos_x += offset_x # correction for gravity that is always negative pos_y -= offset_y return pos_x, pos_y def _get_beamcenter_drop(self): """ Get the beam center drop (delta y) in y diection due to gravity :return delta y: the beam center drop in cm """ # Check if mass == 0 (X-ray). if self.mass == 0: return 0 # Covert unit from A to cm unit_cm = 1e-08 # Velocity of neutron in horizontal direction (~ actual velocity) velocity = _PLANK_H / (self.mass * self.wave.wavelength * unit_cm) # Compute delta y delta_y = 0.5 delta_y *= _GRAVITY sampletodetector = self.sample2detector_distance[0] - \ self.sample2sample_distance[0] delta_y *= sampletodetector delta_y *= (self.source2sample_distance[0] + self.sample2detector_distance[0]) delta_y /= (velocity * velocity) return delta_y