def calc_angles(): '''angles between input basis and candidate basis are calculated''' inp_a, inp_b, inp_c, cand = get_basis() res_a = flex.double() res_b = flex.double() res_c = flex.double() for i in range(len(cand)): for k in range(len(cand[i])): res_a.append(inp_a[i].dot(cand[i][k]) / (inp_a[i].length() * cand[i][k].length())) res_b.append(inp_b[i].dot(cand[i][k]) / (inp_b[i].length() * cand[i][k].length())) res_c.append(inp_c[i].dot(cand[i][k]) / (inp_c[i].length() * cand[i][k].length())) #result is in radians convert to degrees a_angles = flex.acos(res_a) * 180 / math.pi b_angles = flex.acos(res_b) * 180 / math.pi c_angles = flex.acos(res_c) * 180 / math.pi #account for parallel or antiparallel candidate basis vectors for j in range(len(a_angles)): if a_angles[j] >= 90: a_angles[j] = 180 - a_angles[j] for j in range(len(b_angles)): if b_angles[j] >= 90: b_angles[j] = 180 - b_angles[j] for j in range(len(c_angles)): if c_angles[j] >= 90: c_angles[j] = 180 - c_angles[j] return a_angles, b_angles, c_angles
def __call__(self, experiments, reflections, add_correction_column=False): result = flex.reflection_table() for expt_id, experiment in enumerate(experiments): refls = reflections.select(reflections['id'] == expt_id) beam = experiment.beam # Remove the need for pixel size within cxi.merge. Allows multipanel detector with dissimilar panels. # Relies on new frame extractor code called by dials.stills_process that writes s0, s1 and polarization normal # vectors all to the integration pickle. Future path (IE THIS CODE): use dials json and reflection file. s0_vec = matrix.col(beam.get_s0()).normalize() s0_polar_norm = beam.get_polarization_normal() s1_vec = refls['s1'] Ns1 = len(s1_vec) # project the s1_vector onto the plane normal to s0. Get result by subtracting the # projection of s1 onto s0, which is (s1.dot.s0_norm)s0_norm s0_norm = flex.vec3_double(Ns1,s0_vec) s1_proj = (s1_vec.dot(s0_norm))*s0_norm s1_in_normal_plane = s1_vec - s1_proj # Now want the polar angle between the projected s1 and the polarization normal s0_polar_norms = flex.vec3_double(Ns1,s0_polar_norm) dotprod = (s1_in_normal_plane.dot(s0_polar_norms)) costheta = dotprod/(s1_in_normal_plane.norms()) theta = flex.acos(costheta) cos_two_polar_angle = flex.cos(2.0*theta) # gives same as old answer to ~1% but not exact. Not sure why, should not matter. tt_vec = experiment.crystal.get_unit_cell().two_theta(miller_indices = refls['miller_index'], wavelength = beam.get_wavelength()) cos_tt_vec = flex.cos(tt_vec) sin_tt_vec = flex.sin(tt_vec) cos_sq_tt_vec = cos_tt_vec * cos_tt_vec sin_sq_tt_vec = sin_tt_vec * sin_tt_vec P_nought_vec = 0.5 * (1. + cos_sq_tt_vec) F_prime = -1.0 # Hard-coded value defines the incident polarization axis P_prime = 0.5 * F_prime * cos_two_polar_angle * sin_sq_tt_vec # added as a diagnostic #prange=P_nought_vec - P_prime #other_F_prime = 1.0 #otherP_prime = 0.5 * other_F_prime * cos_two_polar_angle * sin_sq_tt_vec #otherprange=P_nought_vec - otherP_prime #diff2 = flex.abs(prange - otherprange) #print >> out, "mean diff is",flex.mean(diff2), "range",flex.min(diff2), flex.max(diff2) # done correction = 1 / ( P_nought_vec - P_prime ) refls['intensity.sum.value'] = refls['intensity.sum.value'] * correction refls['intensity.sum.variance'] = refls['intensity.sum.variance'] * correction**2 # propagated error # This corrects observations for polarization assuming 100% polarization on # one axis (thus the F_prime = -1.0 rather than the perpendicular axis, 1.0) # Polarization model as described by Kahn, Fourme, Gadet, Janin, Dumas & Andre # (1982) J. Appl. Cryst. 15, 330-337, equations 13 - 15. if add_correction_column: refls['polarization_correction'] = correction result.extend(refls) return result
def run(self, experiments, reflections): self.logger.log_step_time("POLARIZATION_CORRECTION") result = flex.reflection_table() for experiment in experiments: refls = reflections.select( reflections['exp_id'] == experiment.identifier) if len(refls) == 0: continue beam = experiment.beam # Remove the need for pixel size within cxi.merge. Allows multipanel detector with dissimilar panels. # Relies on new frame extractor code called by dials.stills_process that writes s0, s1 and polarization normal # vectors all to the integration pickle. Future path (IE THIS CODE): use dials json and reflection file. s0_vec = matrix.col(beam.get_s0()).normalize() s0_polar_norm = beam.get_polarization_normal() s1_vec = refls['s1'] Ns1 = len(s1_vec) # project the s1_vector onto the plane normal to s0. Get result by subtracting the # projection of s1 onto s0, which is (s1.dot.s0_norm)s0_norm s0_norm = flex.vec3_double(Ns1, s0_vec) s1_proj = (s1_vec.dot(s0_norm)) * s0_norm s1_in_normal_plane = s1_vec - s1_proj # Now want the polar angle between the projected s1 and the polarization normal s0_polar_norms = flex.vec3_double(Ns1, s0_polar_norm) dotprod = (s1_in_normal_plane.dot(s0_polar_norms)) costheta = dotprod / (s1_in_normal_plane.norms()) theta = flex.acos(costheta) cos_two_polar_angle = flex.cos(2.0 * theta) # gives same as old answer to ~1% but not exact. Not sure why, should not matter. tt_vec = experiment.crystal.get_unit_cell().two_theta( miller_indices=refls['miller_index'], wavelength=beam.get_wavelength()) cos_tt_vec = flex.cos(tt_vec) sin_tt_vec = flex.sin(tt_vec) cos_sq_tt_vec = cos_tt_vec * cos_tt_vec sin_sq_tt_vec = sin_tt_vec * sin_tt_vec P_nought_vec = 0.5 * (1. + cos_sq_tt_vec) F_prime = -1.0 # Hard-coded value defines the incident polarization axis P_prime = 0.5 * F_prime * cos_two_polar_angle * sin_sq_tt_vec # added as a diagnostic #prange=P_nought_vec - P_prime #other_F_prime = 1.0 #otherP_prime = 0.5 * other_F_prime * cos_two_polar_angle * sin_sq_tt_vec #otherprange=P_nought_vec - otherP_prime #diff2 = flex.abs(prange - otherprange) #print >> out, "mean diff is",flex.mean(diff2), "range",flex.min(diff2), flex.max(diff2) # done correction = 1 / (P_nought_vec - P_prime) refls['intensity.sum.value'] = refls[ 'intensity.sum.value'] * correction refls['intensity.sum.variance'] = refls[ 'intensity.sum.variance'] * correction**2 # propagated error # This corrects observations for polarization assuming 100% polarization on # one axis (thus the F_prime = -1.0 rather than the perpendicular axis, 1.0) # Polarization model as described by Kahn, Fourme, Gadet, Janin, Dumas & Andre # (1982) J. Appl. Cryst. 15, 330-337, equations 13 - 15. result.extend(refls) if len(reflections) > 0: self.logger.log( "Applied polarization correction. Mean intensity changed from %.2f to %.2f" % (flex.mean(reflections['intensity.sum.value']), flex.mean(result['intensity.sum.value']))) self.logger.log_step_time("POLARIZATION_CORRECTION", True) self.logger.log("Memory usage: %d MB" % get_memory_usage()) # Remove 's1' column from the reflection table from xfel.merging.application.reflection_table_utils import reflection_table_utils reflections = reflection_table_utils.prune_reflection_table_keys( reflections=result, keys_to_delete=['s1']) self.logger.log("Pruned reflection table") self.logger.log("Memory usage: %d MB" % get_memory_usage()) return experiments, reflections
def wavelengths_from_gaussians(experiments, reflections, mosaic_parameters): """ Given a set of mosaic parameters, use gaussian bandpass and mosaicity to estimate a wavelength for each reflection. Details: For a given reflection, the reciprocal lattice point vector q = Ah, where A is the reciprocal A matrix and h is the reflection's miller index. Construct a vector e1 orthagonal to s0 and q. @param experiments ExperimentList. If crystal.band_pass is set, use it. Otherwise, estimate the band pass using estimate_bandpass @param reflections flex.reflection_table Needs to contain the column reflection_wavelength_from_pixels @param mosaic_parameters Tuple of domain size (angstroms) and half mosaic angle (degrees). If None, use the mosaic parameters from each crystal model. """ if "reflection_wavelength_from_pixels" not in reflections: return reflections if mosaic_parameters is not None: domain_size_ang, half_mosaicity_deg = mosaic_parameters print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) table = flex.reflection_table() new_wavelengths = flex.double() def gaussian_product(mean1, sigma1, mean2, sigma2): """Jiffy function to multiply two gaussians. Formula from P. Bromiley, "Products and convolutions of Gaussian distributions," Medical School, Univ. Manchester, Manchester, UK, Tech. Rep, vol. 3, p. 2003, 2003. """ ssq1 = sigma1**2 ssq2 = sigma2**2 mean = ((mean1 * ssq2) + (mean2 * ssq1)) / (ssq1 + ssq2) sigma = flex.sqrt((ssq1 * ssq2) / (ssq1 + ssq2)) return mean, sigma for expt_id, expt in enumerate(experiments): refls = reflections.select(reflections["id"] == expt_id) table.extend(refls) if mosaic_parameters is None: domain_size_ang = expt.crystal.get_domain_size_ang() half_mosaicity_deg = expt.crystal.get_half_mosaicity_deg() print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) # Determine how to obtain the bandpass if hasattr(expt.crystal, "bandpass"): wavelength_min, wavelength_max = expt.crystal.bandpass else: wavelength_min, wavelength_max = estimate_bandpass(refls) expt.crystal.bandpass = wavelength_min, wavelength_max unit_s0 = flex.vec3_double(len(refls), expt.beam.get_s0()).each_normalize() wavelength = expt.beam.get_wavelength() q = (flex.mat3_double(len(refls), expt.crystal.get_A()) * refls["miller_index"].as_vec3_double()) e1 = q.cross(unit_s0).each_normalize() # length of an arc l = 2pir * angle/2pi = r*angle. So angle = l/r combined_mosaic_angle_approximation = ( (2 / domain_size_ang) / q.norms()) + (half_mosaicity_deg * math.pi / 180) # Compute z angles # Angle between s0 and q z_mosaicity = math.pi - q.angle(unit_s0) # Angle between s0 and q rotated to be on the center of bandpass z_wavelength = flex.acos(wavelength * q.norms() / 2) # Angles between s0 and q vectors rotated to the extreme ends of the bandpass z_wavelength_min = flex.acos(wavelength_min * q.norms() / 2) z_wavelength_max = flex.acos(wavelength_max * q.norms() / 2) # Now assume two gaussians on a polar notation (IE x is angle between a q vector and s0). One gaussian for the # bandpass (assuming the width of the bandpass is one sigma) and one for the mosaicity (assuming the width of # the mosaic spread is one sigma). The product of the two gaussians is the illuminated volume, and the center # of that gaussian angle a q vector would take with s0 such that it maximizes the illuminated volume. mean_z, sigma_z = gaussian_product( z_wavelength, z_wavelength - z_wavelength_max, z_mosaicity, combined_mosaic_angle_approximation, ) # Compute the wavelengths given the illuminated volume angle mean_z lmbda = flex.cos(mean_z) / (q.norms() / 2) new_wavelengths.extend(lmbda) assert (new_wavelengths <= 0).count(True) == 0 reflections[ "reflection_wavelength_from_gaussian_mosaicity_and_bandpass"] = new_wavelengths return reflections
def tophat_vector_wavelengths(experiments, reflections, mosaic_parameters): """ Given a set of mosaic parameters, use vectors to estimate a wavelength for each reflection Details: For a given reflection, the reciprocal lattice point vector q = Ah, where A is the reciprocal A matrix and h is the reflection's miller index. Construct a vector e1 orthagonal to s0 and q. Construct 4 more vectors of length equal to the magnitude of q, and that lie in the plane that is normal to e1: q_mos_inner and q_mos_outer: q vectors rotated into or out of the Ewald sphere by an angle equal to the half mosaic angle + the angle inscribed by adding an arc length equal to 2/domain size (angstroms). Call this angle the combined mosaic angle approximation. q_wavelength_min and q_wavelength_max: q vectors rotated on an ewald sphere with radius 1/bandpass minimum or 1/bandpass maximum. Consider now the pairs of vectors wavelength min/max and q_mos inner/outer. If neither q_mos vectors lies between the two wavelength vectors, then assign a refletion's wavelength to either wavelength min or max, depending on which is closest. Otherwise, find two vectors that lie between wavelength min/max. For example if q_mos inner lies between them, but outer does not, the two vectors will be q_mos inner and q wavelength min. If both q_mos inner and outer lie between wavelength min/max, then the two vetors will be q_mos inner and outer. Once the two vectors have been identified, define a new q vector which is the average of the two vectors. Determine the wavelength that would allow this q vector to be in the diffracting condition. Report that wavelength in the column reflection_wavelength_from_mosaicity_and_bandpass. Because these determinations involve hard cutoffs instead of gaussians, the wavelengths are determined in a manner similar to the overlap of tophat functions. @param experiments ExperimentList. If crystal.band_pass is set, use it. Otherwise, estimate the band pass using estimate_bandpass @param reflections flex.reflection_table Needs to contain the column reflection_wavelength_from_pixels @param mosaic_parameters Tuple of domain size (angstroms) and half mosaic angle (degrees) """ if "reflection_wavelength_from_pixels" not in reflections: return reflections domain_size_ang, half_mosaicity_deg = mosaic_parameters print( "Computing per-reflection wavelengths from domain size", domain_size_ang, "(ang), half mosaic angle", half_mosaicity_deg, "(deg), and bandpass derived from each image", ) table = flex.reflection_table() new_wavelengths = flex.double() # Keep track of the various cases case_0 = case_1 = case_2 = case_3 = case_4 = case_5 = 0 for expt_id, expt in enumerate(experiments): refls = reflections.select(reflections["id"] == expt_id) table.extend(refls) # Determine how to obtain the bandpass if hasattr(expt.crystal, "bandpass"): wavelength_min, wavelength_max = expt.crystal.bandpass else: wavelength_min, wavelength_max = estimate_bandpass(refls) expt.crystal.bandpass = wavelength_min, wavelength_max unit_s0 = flex.vec3_double(len(refls), expt.beam.get_s0()).each_normalize() wavelength = expt.beam.get_wavelength() q = (flex.mat3_double(len(refls), expt.crystal.get_A()) * refls["miller_index"].as_vec3_double()) e1 = q.cross(unit_s0).each_normalize() # length of an arc l = 2pir * angle/2pi = r*angle. So angle = l/r combined_mosaic_angle_approximation = ( (2 / domain_size_ang) / q.norms()) + (half_mosaicity_deg * math.pi / 180) q_mos_inner = q.rotate_around_origin( e1, -combined_mosaic_angle_approximation) q_mos_outer = q.rotate_around_origin( e1, combined_mosaic_angle_approximation) # Z: angle between q and s0 z_mos_inner = math.pi - q_mos_inner.angle(unit_s0) z_mos_outer = math.pi - q_mos_outer.angle(unit_s0) lmbda_bigger = flex.cos(z_mos_inner) / (q.norms() / 2) lmbda_smaller = flex.cos(z_mos_outer) / (q.norms() / 2) z_wavelength_min = flex.acos(wavelength_min * q.norms() / 2) z_wavelength_max = flex.acos(wavelength_max * q.norms() / 2) q_wavelength_min = ( unit_s0.rotate_around_origin(e1, math.pi + z_wavelength_min) * q.norms()) q_wavelength_max = ( unit_s0.rotate_around_origin(e1, math.pi + z_wavelength_max) * q.norms()) assert (lmbda_smaller < lmbda_bigger).count(False) == 0 sel = flex.bool(len(refls), True) image_wavelengths = flex.double(len(refls), 0) q_inner = flex.vec3_double() q_outer = flex.vec3_double() # Iterate through the reflections and sort each into one of the 6 cases for i in xrange(len(refls)): # Cases 0 and 1: both q_mos inner and outer are outside of the wavelength min/max vectors if lmbda_smaller[i] > wavelength_max: image_wavelengths[i] = wavelength_max sel[i] = False case_0 += 1 continue elif lmbda_bigger[i] < wavelength_min: image_wavelengths[i] = wavelength_min sel[i] = False case_1 += 1 continue sel[i] = True # Case 2: q_mos outer is between wavelengths min and max so use q_mos outer # Case 3: q_mos outer is outside of wavelength min so use wavelength min if lmbda_smaller[i] >= wavelength_min: q_outer.append(q_mos_outer[i]) case_2 += 1 else: q_outer.append(q_wavelength_min[i]) case_3 += 1 # Case 4: q_mos inner is between wavelengths min and max so use q_mos inner # Case 5: q_mos inner is outside of wavelength max so use wavelength max if lmbda_bigger[i] <= wavelength_max: q_inner.append(q_mos_inner[i]) case_4 += 1 else: q_inner.append(q_wavelength_max[i]) case_5 += 1 # Compute new reflection wavelengths new_q = (q_inner + q_outer) * 0.5 z = math.pi - new_q.angle(unit_s0.select(sel)) lmbda = flex.cos(z) / (new_q.norms() / 2) image_wavelengths.set_selected(sel, lmbda) new_wavelengths.extend(image_wavelengths) assert (new_wavelengths <= 0).count(True) == 0 reflections[ "reflection_wavelength_from_mosaicity_and_bandpass"] = new_wavelengths print("CASES", case_0, case_1, case_2, case_3, case_4, case_5) return reflections