def sequence_to_stills(experiments, reflections, params): assert len(reflections) == 1 reflections = reflections[0] new_experiments = ExperimentList() new_reflections = flex.reflection_table() # This is the subset needed to integrate for key in [ "id", "imageset_id", "shoebox", "bbox", "intensity.sum.value", "intensity.sum.variance", "entering", "flags", "miller_index", "panel", "xyzobs.px.value", "xyzobs.px.variance", ]: if key in reflections: new_reflections[key] = type(reflections[key])() elif key == "imageset_id": assert len(experiments.imagesets()) == 1 reflections["imageset_id"] = flex.int(len(reflections), 0) new_reflections["imageset_id"] = flex.int() elif key == "entering": reflections["entering"] = flex.bool(len(reflections), False) new_reflections["entering"] = flex.bool() else: raise RuntimeError( f"Expected key not found in reflection table: {key}") for expt_id, experiment in enumerate(experiments): # Get the goniometr setting matrix goniometer_setting_matrix = matrix.sqr( experiment.goniometer.get_setting_rotation()) goniometer_axis = matrix.col(experiment.goniometer.get_rotation_axis()) step = experiment.scan.get_oscillation()[1] refls = reflections.select(reflections["id"] == expt_id) _, _, _, _, z1, z2 = refls["bbox"].parts() # Create an experiment for each scanpoint for i_scan_point in range(*experiment.scan.get_array_range()): if params.max_scan_points and i_scan_point >= params.max_scan_points: break # The A matrix is the goniometer setting matrix for this scan point # times the scan varying A matrix at this scan point. Note, the # goniometer setting matrix for scan point zero will be the identity # matrix and represents the beginning of the oscillation. # For stills, the A matrix needs to be positioned in the midpoint of an # oscillation step. Hence, here the goniometer setting matrixis rotated # by a further half oscillation step. A = (goniometer_axis.axis_and_angle_as_r3_rotation_matrix( angle=experiment.scan.get_angle_from_array_index(i_scan_point) + (step / 2), deg=True, ) * goniometer_setting_matrix * matrix.sqr( experiment.crystal.get_A_at_scan_point(i_scan_point))) crystal = MosaicCrystalSauter2014(experiment.crystal) crystal.set_A(A) # Copy in mosaic parameters if available if params.output.domain_size_ang is None and hasattr( experiment.crystal, "get_domain_size_ang"): crystal.set_domain_size_ang( experiment.crystal.get_domain_size_ang()) elif params.output.domain_size_ang is not None: crystal.set_domain_size_ang(params.output.domain_size_ang) if params.output.half_mosaicity_deg is None and hasattr( experiment.crystal, "get_half_mosaicity_deg"): crystal.set_half_mosaicity_deg( experiment.crystal.get_half_mosaicity_deg()) elif params.output.half_mosaicity_deg is not None: crystal.set_half_mosaicity_deg( params.output.half_mosaicity_deg) new_experiment = Experiment( detector=experiment.detector, beam=experiment.beam, crystal=crystal, imageset=experiment.imageset.as_imageset() [i_scan_point:i_scan_point + 1], ) new_experiments.append(new_experiment) # Each reflection in a 3D shoebox can be found on multiple images. # Slice the reflections such that any reflection on this scan point # is included with this image new_id = len(new_experiments) - 1 subrefls = refls.select((i_scan_point >= z1) & (i_scan_point < z2)) for refl in subrefls.rows(): assert i_scan_point in range(*refl["bbox"][4:6]) new_sb = Shoebox() start = i_scan_point - refl["bbox"][4] # z1 new_sb.data = refl["shoebox"].data[start:start + 1, :, :] new_sb.background = refl["shoebox"].background[start:start + 1, :, :] new_sb.mask = refl["shoebox"].mask[start:start + 1, :, :] intensity = new_sb.summed_intensity() new_sb.bbox = tuple( list(refl["bbox"])[0:4] + [0, 1]) # keep the original shoebox but reset the z values new_sb.panel = refl["panel"] new_refl = {} new_refl["id"] = new_refl["imageset_id"] = new_id new_refl["shoebox"] = new_sb new_refl["bbox"] = new_sb.bbox new_refl["intensity.sum.value"] = intensity.observed.value new_refl[ "intensity.sum.variance"] = intensity.observed.variance for key in ["entering", "flags", "miller_index", "panel"]: new_refl[key] = refl[key] centroid = new_sb.centroid_foreground_minus_background() new_refl["xyzobs.px.value"] = centroid.px.position new_refl["xyzobs.px.variance"] = centroid.px.variance new_reflections.append({}) for key in new_refl: new_reflections[key][-1] = new_refl[key] # Re-predict using the reflection slices and the stills predictors ref_predictor = ExperimentsPredictorFactory.from_experiments( new_experiments, force_stills=new_experiments.all_stills()) new_reflections = ref_predictor(new_reflections) return (new_experiments, new_reflections)
def __call__(self): """Determine optimal mosaicity and domain size model (monochromatic)""" if self.refinery is None: RR = self.reflections else: RR = self.refinery.predict_for_reflection_table(self.reflections) excursion_rad = RR["delpsical.rad"] delta_psi_deg = excursion_rad * 180. / math.pi print print flex.max(delta_psi_deg), flex.min(delta_psi_deg) mean_excursion = flex.mean(delta_psi_deg) print "The mean excursion is %7.3f degrees, r.m.s.d %7.3f" % ( mean_excursion, math.sqrt(flex.mean(RR["delpsical2"]))) from dxtbx.model import MosaicCrystalSauter2014 crystal = MosaicCrystalSauter2014(self.experiments[0].crystal) self.experiments[0].crystal = crystal beam = self.experiments[0].beam miller_indices = self.reflections["miller_index"] # FIXME XXX revise this formula so as to use a different wavelength potentially for each reflection two_thetas = crystal.get_unit_cell().two_theta(miller_indices, beam.get_wavelength(), deg=True) dspacings = crystal.get_unit_cell().d(miller_indices) dspace_sq = dspacings * dspacings # First -- try to get a reasonable envelope for the observed excursions. ## minimum of three regions; maximum of 50 measurements in each bin print "fitting parameters on %d spots" % len(excursion_rad) n_bins = min(max(3, len(excursion_rad) // 25), 50) bin_sz = len(excursion_rad) // n_bins print "nbins", n_bins, "bin_sz", bin_sz order = flex.sort_permutation(two_thetas) two_thetas_env = flex.double() dspacings_env = flex.double() excursion_rads_env = flex.double() for x in xrange(0, n_bins): subset = order[x * bin_sz:(x + 1) * bin_sz] two_thetas_env.append(flex.mean(two_thetas.select(subset))) dspacings_env.append(flex.mean(dspacings.select(subset))) excursion_rads_env.append( flex.max(flex.abs(excursion_rad.select(subset)))) # Second -- parameter fit ## solve the normal equations sum_inv_u_sq = flex.sum(dspacings_env * dspacings_env) sum_inv_u = flex.sum(dspacings_env) sum_te_u = flex.sum(dspacings_env * excursion_rads_env) sum_te = flex.sum(excursion_rads_env) Normal_Mat = sqr( (sum_inv_u_sq, sum_inv_u, sum_inv_u, len(dspacings_env))) Vector = col((sum_te_u, sum_te)) solution = Normal_Mat.inverse() * Vector s_ang = 1. / (2 * solution[0]) print "Best LSQ fit Scheerer domain size is %9.2f ang" % (s_ang) tan_phi_rad = dspacings / (2. * s_ang) tan_phi_deg = tan_phi_rad * 180. / math.pi k_degrees = solution[1] * 180. / math.pi print "The LSQ full mosaicity is %8.5f deg; half-mosaicity %9.5f" % ( 2 * k_degrees, k_degrees) tan_outer_deg = tan_phi_deg + k_degrees from xfel.mono_simulation.max_like import minimizer # coerce the estimates to be positive for max-likelihood lower_limit_domain_size = math.pow( crystal.get_unit_cell().volume(), 1. / 3.) * 3 # params.refinement.domain_size_lower_limit d_estimate = max(s_ang, lower_limit_domain_size) M = minimizer(d_i=dspacings, psi_i=excursion_rad, eta_rad=abs(2. * solution[1]), Deff=d_estimate) print "ML: mosaicity FW=%4.2f deg, Dsize=%5.0fA on %d spots" % ( M.x[1] * 180. / math.pi, 2. / M.x[0], len(two_thetas)) tan_phi_rad_ML = dspacings / (2. / M.x[0]) tan_phi_deg_ML = tan_phi_rad_ML * 180. / math.pi tan_outer_deg_ML = tan_phi_deg_ML + 0.5 * M.x[1] * 180. / math.pi self.nv_acceptance_flags = flex.abs(delta_psi_deg) < tan_outer_deg_ML if self.graph_verbose: #params.refinement.mosaic.enable_AD14F7B: # Excursion vs resolution fit AD1TF7B_MAX2T = 30. AD1TF7B_MAXDP = 1. from matplotlib import pyplot as plt plt.plot(two_thetas, delta_psi_deg, "bo") minplot = flex.min(two_thetas) plt.plot([0, minplot], [mean_excursion, mean_excursion], "k-") LR = flex.linear_regression(two_thetas, delta_psi_deg) model_y = LR.slope() * two_thetas + LR.y_intercept() plt.plot(two_thetas, model_y, "k-") plt.title("ML: mosaicity FW=%4.2f deg, Dsize=%5.0fA on %d spots" % (M.x[1] * 180. / math.pi, 2. / M.x[0], len(two_thetas))) plt.plot(two_thetas, tan_phi_deg_ML, "r.") plt.plot(two_thetas, -tan_phi_deg_ML, "r.") plt.plot(two_thetas, tan_outer_deg_ML, "g.") plt.plot(two_thetas, -tan_outer_deg_ML, "g.") plt.xlim([0, AD1TF7B_MAX2T]) plt.ylim([-AD1TF7B_MAXDP, AD1TF7B_MAXDP]) plt.show() plt.close() from xfel.mono_simulation.util import green_curve_area self.green_curve_area = green_curve_area(two_thetas, tan_outer_deg_ML) print "The green curve area is ", self.green_curve_area crystal.set_half_mosaicity_deg(M.x[1] * 180. / (2. * math.pi)) crystal.set_domain_size_ang(2. / M.x[0]) self._ML_full_mosaicity_rad = M.x[1] self._ML_domain_size_ang = 2. / M.x[0] #params.refinement.mosaic.model_expansion_factor """The expansion factor should be initially set to 1, then expanded so that the # reflections matched becomes as close as possible to # of observed reflections input, in the last integration call. Determine this by inspecting the output log file interactively. Do not exceed the bare minimum threshold needed. The intention is to find an optimal value, global for a given dataset.""" model_expansion_factor = 1.4 crystal.set_half_mosaicity_deg(crystal.get_half_mosaicity_deg() * model_expansion_factor) crystal.set_domain_size_ang(crystal.get_domain_size_ang() / model_expansion_factor) return crystal
def __init__(self, test_nave_model=False): # Set up experimental models with regular geometry from dxtbx.model import BeamFactory from dxtbx.model import GoniometerFactory from dxtbx.model import DetectorFactory # Beam along the Z axis self.beam = BeamFactory.make_beam(unit_s0=matrix.col((0, 0, 1)), wavelength=1.0) # Goniometer (used only for index generation) along X axis self.goniometer = GoniometerFactory.known_axis(matrix.col((1, 0, 0))) # Detector fast, slow along X, -Y; beam in the centre, 200 mm distance dir1 = matrix.col((1, 0, 0)) dir2 = matrix.col((0, -1, 0)) n = matrix.col((0, 0, 1)) centre = matrix.col((0, 0, 200)) npx_fast = npx_slow = 1000 pix_size = 0.2 origin = centre - (0.5 * npx_fast * pix_size * dir1 + 0.5 * npx_slow * pix_size * dir2) self.detector = DetectorFactory.make_detector( "PAD", dir1, dir2, origin, (pix_size, pix_size), (npx_fast, npx_slow), (0, 1.0e6), ) # Cubic 100 A^3 crystal a = matrix.col((100, 0, 0)) b = matrix.col((0, 100, 0)) c = matrix.col((0, 0, 100)) if test_nave_model: from dxtbx.model import MosaicCrystalSauter2014 self.crystal = MosaicCrystalSauter2014(a, b, c, space_group_symbol="P 1") self.crystal.set_half_mosaicity_deg(500) self.crystal.set_domain_size_ang(0.2) else: from dxtbx.model import Crystal self.crystal = Crystal(a, b, c, space_group_symbol="P 1") # Collect these models in an Experiment (ignoring the goniometer) from dxtbx.model.experiment_list import Experiment self.experiment = Experiment( beam=self.beam, detector=self.detector, goniometer=None, scan=None, crystal=self.crystal, imageset=None, ) # Generate some reflections self.reflections = self.generate_reflections()