def predict_to_miller_set_with_shadow(self, expt, resolution): predicted = flex.reflection_table.from_predictions(expt, dmin=resolution) # transmogrify this to an ExperimentList from an Experiment experiments = ExperimentList() experiments.append(expt) predicted["id"] = flex.int(predicted.size(), 0) shadowed = filter_shadowed_reflections( experiments, predicted, experiment_goniometer=True ) predicted = predicted.select(~shadowed) hkl = predicted["miller_index"] # now get a full set of all unique miller indices obs = miller.set( crystal_symmetry=crystal.symmetry( space_group=expt.crystal.get_space_group(), unit_cell=expt.crystal.get_unit_cell(), ), anomalous_flag=True, indices=hkl, ).unique_under_symmetry() return obs, shadowed
def predict_to_miller_set_with_shadow(self, expt, resolution): from dials.array_family import flex from dials.algorithms.shadowing.filter import filter_shadowed_reflections predicted = flex.reflection_table.from_predictions(expt, dmin=resolution) # transmogrify this to an ExperimentList from an Experiment from dxtbx.model import ExperimentList experiments = ExperimentList() experiments.append(expt) predicted['id'] = flex.int(predicted.size(), 0) shadowed = filter_shadowed_reflections(experiments, predicted, experiment_goniometer=True) predicted = predicted.select(~shadowed) hkl = predicted['miller_index'] # now get a full set of all unique miller indices from cctbx import miller from cctbx import crystal obs = miller.set(crystal_symmetry=crystal.symmetry( space_group=expt.crystal.get_space_group(), unit_cell=expt.crystal.get_unit_cell()), anomalous_flag=True, indices=hkl).unique_under_symmetry() return obs, shadowed
def run(self): '''Execute the script.''' from dials.util.command_line import Command from dials.array_family import flex from dials.util.options import flatten_experiments # Parse the command line params, options = self.parser.parse_args(show_diff_phil=True) # Check the number of experiments experiments = flatten_experiments(params.input.experiments) if len(experiments) == 0: self.parser.print_help() return predicted_all = flex.reflection_table() for i_expt, expt in enumerate(experiments): if params.buffer_size > 0: # Hack to make the predicter predict reflections outside of the range # of the scan scan = expt.scan image_range = scan.get_image_range() oscillation = scan.get_oscillation() scan.set_image_range((image_range[0]-params.buffer_size, image_range[1]+params.buffer_size)) scan.set_oscillation((oscillation[0]-params.buffer_size*oscillation[1], oscillation[1])) # Populate the reflection table with predictions predicted = flex.reflection_table.from_predictions( expt, force_static=params.force_static, dmin=params.d_min) predicted['id'] = flex.int(len(predicted), i_expt) predicted_all.extend(predicted) # if we are not ignoring shadows, look for reflections in the masked # region, see https://github.com/dials/dials/issues/349 if not params.ignore_shadows: from dials.algorithms.shadowing.filter import filter_shadowed_reflections shadowed = filter_shadowed_reflections(experiments, predicted_all, experiment_goniometer=True) predicted_all = predicted_all.select(~shadowed) try: predicted_all.compute_bbox(experiments) except Exception: pass # Save the reflections to file Command.start('Saving {0} reflections to {1}'.format( len(predicted_all), params.output)) predicted_all.as_pickle(params.output) Command.end('Saved {0} reflections to {1}'.format( len(predicted_all), params.output))
def run(self, args=None): """Execute the script.""" # Parse the command line params, options = self.parser.parse_args(args, show_diff_phil=True) # Check the number of experiments experiments = flatten_experiments(params.input.experiments) if len(experiments) == 0: self.parser.print_help() return predicted_all = flex.reflection_table() for i_expt, expt in enumerate(experiments): if params.buffer_size > 0: # Hack to make the predicter predict reflections outside of the range # of the scan scan = expt.scan image_range = scan.get_image_range() oscillation = scan.get_oscillation() scan.set_image_range(( image_range[0] - params.buffer_size, image_range[1] + params.buffer_size, )) scan.set_oscillation(( oscillation[0] - params.buffer_size * oscillation[1], oscillation[1], )) # Populate the reflection table with predictions predicted = flex.reflection_table.from_predictions( expt, force_static=params.force_static, dmin=params.d_min) predicted["id"] = flex.int(len(predicted), i_expt) predicted_all.extend(predicted) # if we are not ignoring shadows, look for reflections in the masked # region, see https://github.com/dials/dials/issues/349 if not params.ignore_shadows: shadowed = filter_shadowed_reflections(experiments, predicted_all, experiment_goniometer=True) predicted_all = predicted_all.select(~shadowed) try: predicted_all.compute_bbox(experiments) except Exception: pass # Save the reflections to file Command.start( f"Saving {len(predicted_all)} reflections to {params.output}") predicted_all.as_file(params.output) Command.end( f"Saved {len(predicted_all)} reflections to {params.output}")
def test_filter_shadowed_reflections(dials_regression): experiments_json = os.path.join( dials_regression, "shadow_test_data/DLS_I04_SmarGon/experiments.json") predicted_pickle = os.path.join( dials_regression, "shadow_test_data/DLS_I04_SmarGon/predicted.pickle") experiments = load.experiment_list(experiments_json, check_format=True) predicted = flex.reflection_table.from_file(predicted_pickle) for experiment_goniometer in (True, False): shadowed = filter_shadowed_reflections( experiments, predicted, experiment_goniometer=experiment_goniometer) assert shadowed.count(True) == 17 assert shadowed.count(False) == 674
def test_filter_shadowed_reflections(dials_regression): experiments_json = os.path.join( dials_regression, "shadow_test_data/DLS_I04_SmarGon/experiments.json") predicted_pickle = os.path.join( dials_regression, "shadow_test_data/DLS_I04_SmarGon/predicted.pickle") from dxtbx.serialize import load experiments = load.experiment_list(experiments_json, check_format=True) from libtbx import easy_pickle predicted = easy_pickle.load(predicted_pickle) from dials.algorithms.shadowing.filter import filter_shadowed_reflections for experiment_goniometer in (True, False): shadowed = filter_shadowed_reflections( experiments, predicted, experiment_goniometer=experiment_goniometer) assert shadowed.count(True) == 17 assert shadowed.count(False) == 674
def _integrate_finish(self): """Finish off the integration by running correct.""" # first run the postrefinement etc with spacegroup P1 # and the current unit cell - this will be used to # obtain a benchmark rmsd in pixels / phi and also # cell deviations (this is working towards spotting bad # indexing solutions) - only do this if we have no # reindex matrix... and no postrefined cell... p1_deviations = None # fix for bug # 3264 - # if we have not run integration with refined parameters, make it so... # erm? shouldn't this therefore return if this is the principle, or # set the flag after we have tested the lattice? if ("GXPARM.XDS" not in self._xds_data_files and PhilIndex.params.xds.integrate.reintegrate): logger.debug( "Resetting integrater, to ensure refined orientation is used") self.set_integrater_done(False) if (not self.get_integrater_reindex_matrix() and not self._intgr_cell and PhilIndex.params.xia2.settings.lattice_rejection and not self.get_integrater_sweep().get_user_lattice()): correct = self.Correct() correct.set_data_range( self._intgr_wedge[0] + self.get_frame_offset(), self._intgr_wedge[1] + self.get_frame_offset(), ) if self.get_polarization() > 0.0: correct.set_polarization(self.get_polarization()) # FIXME should this be using the correctly transformed # cell or are the results ok without it?! correct.set_spacegroup_number(1) correct.set_cell(self._intgr_refiner_cell) correct.run() cell = correct.get_result("cell") cell_esd = correct.get_result("cell_esd") logger.debug("Postrefinement in P1 results:") logger.debug("%7.3f %7.3f %7.3f %7.3f %7.3f %7.3f" % tuple(cell)) logger.debug("%7.3f %7.3f %7.3f %7.3f %7.3f %7.3f" % tuple(cell_esd)) logger.debug("Deviations: %.2f pixels %.2f degrees" % (correct.get_result("rmsd_pixel"), correct.get_result("rmsd_phi"))) p1_deviations = ( correct.get_result("rmsd_pixel"), correct.get_result("rmsd_phi"), ) # next run the postrefinement etc with the given # cell / lattice - this will be the assumed result... integrate_hkl = os.path.join(self.get_working_directory(), "INTEGRATE.HKL") if PhilIndex.params.xia2.settings.input.format.dynamic_shadowing: from dxtbx.serialize import load from dials.algorithms.shadowing.filter import filter_shadowed_reflections experiments_json = xparm_xds_to_experiments_json( os.path.join(self.get_working_directory(), "XPARM.XDS"), self.get_working_directory(), ) experiments = load.experiment_list(experiments_json, check_format=True) imageset = experiments[0].imageset masker = (imageset.get_format_class().get_instance( imageset.paths()[0]).get_masker()) if masker is not None: integrate_filename = integrate_hkl_to_reflection_file( integrate_hkl, experiments_json, self.get_working_directory()) reflections = flex.reflection_table.from_file( integrate_filename) t0 = time.time() sel = filter_shadowed_reflections(experiments, reflections) shadowed = reflections.select(sel) t1 = time.time() logger.debug("Filtered %i reflections in %.1f seconds" % (sel.count(True), t1 - t0)) filter_hkl = os.path.join(self.get_working_directory(), "FILTER.HKL") with open(filter_hkl, "wb") as f: detector = experiments[0].detector for ref in shadowed: p = detector[ref["panel"]] ox, oy = p.get_raw_image_offset() h, k, l = ref["miller_index"] x, y, z = ref["xyzcal.px"] dx, dy, dz = (2, 2, 2) print( "%i %i %i %.1f %.1f %.1f %.1f %.1f %.1f" % (h, k, l, x + ox, y + oy, z, dx, dy, dz), file=f, ) t2 = time.time() logger.debug("Written FILTER.HKL in %.1f seconds" % (t2 - t1)) correct = self.Correct() correct.set_data_range( self._intgr_wedge[0] + self.get_frame_offset(), self._intgr_wedge[1] + self.get_frame_offset(), ) if self.get_polarization() > 0.0: correct.set_polarization(self.get_polarization()) # BUG # 2695 probably comes from here - need to check... # if the pointless interface comes back with a different # crystal setting then the unit cell stored in self._intgr_cell # needs to be set to None... if self.get_integrater_spacegroup_number(): correct.set_spacegroup_number( self.get_integrater_spacegroup_number()) if not self._intgr_cell: raise RuntimeError("no unit cell to recycle") correct.set_cell(self._intgr_cell) # BUG # 3113 - new version of XDS will try and figure the # best spacegroup out from the intensities (and get it wrong!) # unless we set the spacegroup and cell explicitly if not self.get_integrater_spacegroup_number(): cell = self._intgr_refiner_cell lattice = self._intgr_refiner.get_refiner_lattice() spacegroup_number = lattice_to_spacegroup_number(lattice) # this should not prevent the postrefinement from # working correctly, else what is above would not # work correctly (the postrefinement test) correct.set_spacegroup_number(spacegroup_number) correct.set_cell(cell) logger.debug("Setting spacegroup to: %d" % spacegroup_number) logger.debug("Setting cell to: %.2f %.2f %.2f %.2f %.2f %.2f" % tuple(cell)) if self.get_integrater_reindex_matrix(): # bug! if the lattice is not primitive the values in this # reindex matrix need to be multiplied by a constant which # depends on the Bravais lattice centering. lattice = self._intgr_refiner.get_refiner_lattice() matrix = self.get_integrater_reindex_matrix() matrix = scitbx.matrix.sqr(matrix).transpose().elems matrix = r_to_rt(matrix) if lattice[1] == "P": mult = 1 elif lattice[1] == "C" or lattice[1] == "I": mult = 2 elif lattice[1] == "R": mult = 3 elif lattice[1] == "F": mult = 4 else: raise RuntimeError("unknown multiplier for lattice %s" % lattice) logger.debug("REIDX multiplier for lattice %s: %d" % (lattice, mult)) mult_matrix = [mult * m for m in matrix] logger.debug("REIDX set to %d %d %d %d %d %d %d %d %d %d %d %d" % tuple(mult_matrix)) correct.set_reindex_matrix(mult_matrix) correct.run() # record the log file - pname, xname, dname = self.get_integrater_project_info() sweep = self.get_integrater_sweep_name() FileHandler.record_log_file( f"{pname} {xname} {dname} {sweep} CORRECT", os.path.join(self.get_working_directory(), "CORRECT.LP"), ) FileHandler.record_more_data_file( f"{pname} {xname} {dname} {sweep} CORRECT", os.path.join(self.get_working_directory(), "XDS_ASCII.HKL"), ) # erm. just to be sure if self.get_integrater_reindex_matrix() and correct.get_reindex_used(): raise RuntimeError("Reindex panic!") # get the reindex operation used, which may be useful if none was # set but XDS decided to apply one, e.g. #419. if not self.get_integrater_reindex_matrix( ) and correct.get_reindex_used(): # convert this reindex operation to h, k, l form: n.b. this # will involve dividing through by the lattice centring multiplier matrix = rt_to_r(correct.get_reindex_used()) matrix = scitbx.matrix.sqr(matrix).transpose().elems lattice = self._intgr_refiner.get_refiner_lattice() if lattice[1] == "P": mult = 1.0 elif lattice[1] == "C" or lattice[1] == "I": mult = 2.0 elif lattice[1] == "R": mult = 3.0 elif lattice[1] == "F": mult = 4.0 matrix = [m / mult for m in matrix] reindex_op = mat_to_symop(matrix) # assign this to self: will this reset?! make for a leaky # abstraction and just assign this... # self.set_integrater_reindex_operator(reindex) self._intgr_reindex_operator = reindex_op # record the log file - pname, xname, dname = self.get_integrater_project_info() sweep = self.get_integrater_sweep_name() FileHandler.record_log_file( f"{pname} {xname} {dname} {sweep} CORRECT", os.path.join(self.get_working_directory(), "CORRECT.LP"), ) # should get some interesting stuff from the XDS correct file # here, for instance the resolution range to use in integration # (which should be fed back if not fast) and so on... self._intgr_corrected_hklout = os.path.join( self.get_working_directory(), "XDS_ASCII.HKL") # also record the batch range - needed for the analysis of the # radiation damage in chef... self._intgr_batches_out = (self._intgr_wedge[0], self._intgr_wedge[1]) # FIXME perhaps I should also feedback the GXPARM file here?? for file in ["GXPARM.XDS"]: self._xds_data_files[file] = correct.get_output_data_file(file) # record the postrefined cell parameters self._intgr_cell = correct.get_result("cell") self._intgr_n_ref = correct.get_result("n_ref") logger.debug('Postrefinement in "correct" spacegroup results:') logger.debug("%7.3f %7.3f %7.3f %7.3f %7.3f %7.3f" % tuple(correct.get_result("cell"))) logger.debug("%7.3f %7.3f %7.3f %7.3f %7.3f %7.3f" % tuple(correct.get_result("cell_esd"))) logger.debug( "Deviations: %.2f pixels %.2f degrees" % (correct.get_result("rmsd_pixel"), correct.get_result("rmsd_phi"))) logger.debug("Error correction parameters: A=%.3f B=%.3f" % correct.get_result("sdcorrection")) # compute misorientation of axes xparm_file = os.path.join(self.get_working_directory(), "GXPARM.XDS") handle = xparm.reader() handle.read_file(xparm_file) rotn = handle.rotation_axis beam = handle.beam_vector dot = sum(rotn[j] * beam[j] for j in range(3)) r = math.sqrt(sum(rotn[j] * rotn[j] for j in range(3))) b = math.sqrt(sum(beam[j] * beam[j] for j in range(3))) rtod = 180.0 / math.pi angle = rtod * math.fabs(0.5 * math.pi - math.acos(dot / (r * b))) logger.debug("Axis misalignment %.2f degrees" % angle) correct_deviations = ( correct.get_result("rmsd_pixel"), correct.get_result("rmsd_phi"), ) if p1_deviations: # compare and reject if both > 50% higher - though adding a little # flexibility - 0.5 pixel / osc width slack. pixel = p1_deviations[0] phi = math.sqrt(0.05 * 0.05 + p1_deviations[1] * p1_deviations[1]) threshold = PhilIndex.params.xia2.settings.lattice_rejection_threshold logger.debug("RMSD ratio: %.2f" % (correct_deviations[0] / pixel)) logger.debug("RMSPhi ratio: %.2f" % (correct_deviations[1] / phi)) if (correct_deviations[0] / pixel > threshold and correct_deviations[1] / phi > threshold): logger.info( "Eliminating this indexing solution as postrefinement") logger.info("deviations rather high relative to triclinic") raise BadLatticeError( "high relative deviations in postrefinement") if (not PhilIndex.params.dials.fast_mode and not PhilIndex.params.xds.keep_outliers): # check for alien reflections and perhaps recycle - removing them correct_remove = correct.get_remove() if correct_remove: current_remove = set() final_remove = [] # first ensure that there are no duplicate entries... if os.path.exists( os.path.join(self.get_working_directory(), "REMOVE.HKL")): with open( os.path.join(self.get_working_directory(), "REMOVE.HKL")) as fh: for line in fh.readlines(): h, k, l = list(map(int, line.split()[:3])) z = float(line.split()[3]) if (h, k, l, z) not in current_remove: current_remove.add((h, k, l, z)) for c in correct_remove: if c in current_remove: continue final_remove.append(c) logger.debug("%d alien reflections are already removed" % (len(correct_remove) - len(final_remove))) else: # we want to remove all of the new dodgy reflections final_remove = correct_remove z_min = PhilIndex.params.xds.z_min rejected = 0 with open( os.path.join(self.get_working_directory(), "REMOVE.HKL"), "w") as remove_hkl: # write in the old reflections for remove in current_remove: z = remove[3] if z >= z_min: remove_hkl.write("%d %d %d %f\n" % remove) else: rejected += 1 logger.debug("Wrote %d old reflections to REMOVE.HKL" % (len(current_remove) - rejected)) logger.debug("Rejected %d as z < %f" % (rejected, z_min)) # and the new reflections rejected = 0 used = 0 for remove in final_remove: z = remove[3] if z >= z_min: used += 1 remove_hkl.write("%d %d %d %f\n" % remove) else: rejected += 1 logger.debug("Wrote %d new reflections to REMOVE.HKL" % (len(final_remove) - rejected)) logger.debug("Rejected %d as z < %f" % (rejected, z_min)) # we want to rerun the finishing step so... # unless we have added no new reflections... or unless we # have not confirmed the point group (see SCI-398) if used and self.get_integrater_reindex_matrix(): self.set_integrater_finish_done(False) else: logger.debug( "Going quickly so not removing %d outlier reflections..." % len(correct.get_remove())) # Convert INTEGRATE.HKL to MTZ format and reapply any reindexing operations # spacegroup changes to allow use with CCP4 / Aimless for scaling hklout = os.path.splitext(integrate_hkl)[0] + ".mtz" self._factory.set_working_directory(self.get_working_directory()) pointless = self._factory.Pointless() pointless.set_xdsin(integrate_hkl) pointless.set_hklout(hklout) pointless.xds_to_mtz() integrate_mtz = hklout if (self.get_integrater_reindex_operator() or self.get_integrater_spacegroup_number()): logger.debug("Reindexing things to MTZ") reindex = Reindex() reindex.set_working_directory(self.get_working_directory()) auto_logfiler(reindex) if self.get_integrater_reindex_operator(): reindex.set_operator(self.get_integrater_reindex_operator()) if self.get_integrater_spacegroup_number(): reindex.set_spacegroup(self.get_integrater_spacegroup_number()) hklout = "%s_reindex.mtz" % os.path.splitext(integrate_mtz)[0] reindex.set_hklin(integrate_mtz) reindex.set_hklout(hklout) reindex.reindex() integrate_mtz = hklout experiments_json = xparm_xds_to_experiments_json( self._xds_data_files["GXPARM.XDS"], self.get_working_directory()) pname, xname, dname = self.get_integrater_project_info() sweep = self.get_integrater_sweep_name() FileHandler.record_more_data_file(f"{pname} {xname} {dname} {sweep}", experiments_json) FileHandler.record_more_data_file( f"{pname} {xname} {dname} {sweep} INTEGRATE", integrate_mtz) self._intgr_experiments_filename = experiments_json return integrate_mtz
def run(args): from dials.util.options import OptionParser from dials.util.options import flatten_experiments from dials.util.options import flatten_reflections import libtbx.load_env usage = "%s [options] datablock.json" % (libtbx.env.dispatcher_name) parser = OptionParser(usage=usage, phil=phil_scope, read_experiments=True, read_reflections=True, check_format=True, epilog=help_message) params, options = parser.parse_args(show_diff_phil=True) experiments = flatten_experiments(params.input.experiments) reflections = flatten_reflections(params.input.reflections) if len(experiments) == 0 or len(reflections) == 0: parser.print_help() exit(0) from dials.algorithms.shadowing.filter import filter_shadowed_reflections imagesets = experiments.imagesets() reflections = reflections[0] shadowed = filter_shadowed_reflections(experiments, reflections) print "# shadowed reflections: %i/%i (%.2f%%)" % (shadowed.count( True), shadowed.size(), shadowed.count(True) / shadowed.size() * 100) expt = experiments[0] x, y, z = reflections['xyzcal.px'].parts() z_ = z * expt.scan.get_oscillation()[1] zmin, zmax = expt.scan.get_oscillation_range() hist_scan_angle = flex.histogram(z_.select(shadowed), n_slots=int(zmax - zmin)) #hist_scan_angle.show() uc = experiments[0].crystal.get_unit_cell() d_spacings = uc.d(reflections['miller_index']) ds2 = uc.d_star_sq(reflections['miller_index']) hist_res = flex.histogram( ds2.select(shadowed), flex.min(ds2), flex.max(ds2), n_slots=20, ) #hist_res.show() import matplotlib matplotlib.use('Agg') from matplotlib import pyplot as plt plt.hist2d(z_.select(shadowed).as_numpy_array(), ds2.select(shadowed).as_numpy_array(), bins=(40, 40), range=((flex.min(z_), flex.max(z_)), (flex.min(ds2), flex.max(ds2)))) yticks_dsq = flex.double(plt.yticks()[0]) from cctbx import uctbx yticks_d = uctbx.d_star_sq_as_d(yticks_dsq) plt.axes().set_yticklabels(['%.2f' % y for y in yticks_d]) plt.xlabel('Scan angle (degrees)') plt.ylabel('Resolution (A^-1)') cbar = plt.colorbar() cbar.set_label('# shadowed reflections') plt.savefig('n_shadowed_hist2d.png') plt.clf() plt.scatter(hist_scan_angle.slot_centers().as_numpy_array(), hist_scan_angle.slots().as_numpy_array()) plt.xlabel('Scan angle (degrees)') plt.ylabel('# shadowed reflections') plt.savefig("n_shadowed_vs_scan_angle.png") plt.clf() plt.scatter(hist_res.slot_centers().as_numpy_array(), hist_res.slots().as_numpy_array()) plt.xlabel('d_star_sq') plt.savefig("n_shadowed_vs_resolution.png") plt.clf()
def run(args): usage = "%s [options] models.expt" % (libtbx.env.dispatcher_name) parser = OptionParser( usage=usage, phil=phil_scope, read_experiments=True, read_reflections=True, check_format=True, epilog=help_message, ) params, options = parser.parse_args(show_diff_phil=True) experiments = flatten_experiments(params.input.experiments) reflections = flatten_reflections(params.input.reflections) if len(experiments) == 0 or len(reflections) == 0: parser.print_help() exit(0) reflections = reflections[0] shadowed = filter_shadowed_reflections(experiments, reflections) print("# shadowed reflections: %i/%i (%.2f%%)" % ( shadowed.count(True), shadowed.size(), shadowed.count(True) / shadowed.size() * 100, )) expt = experiments[0] z = reflections["xyzcal.px"].parts()[-1] z_ = z * expt.scan.get_oscillation()[1] zmin, zmax = expt.scan.get_oscillation_range() hist_scan_angle = flex.histogram(z_.select(shadowed), n_slots=int(zmax - zmin)) # hist_scan_angle.show() uc = experiments[0].crystal.get_unit_cell() ds2 = uc.d_star_sq(reflections["miller_index"]) hist_res = flex.histogram(ds2.select(shadowed), flex.min(ds2), flex.max(ds2), n_slots=20) # hist_res.show() import matplotlib matplotlib.use("Agg") from matplotlib import pyplot as plt plt.hist2d( z_.select(shadowed).as_numpy_array(), ds2.select(shadowed).as_numpy_array(), bins=(40, 40), range=((flex.min(z_), flex.max(z_)), (flex.min(ds2), flex.max(ds2))), ) yticks_dsq = flex.double(plt.yticks()[0]) yticks_d = uctbx.d_star_sq_as_d(yticks_dsq) plt.axes().set_yticklabels(["%.2f" % y for y in yticks_d]) plt.xlabel("Scan angle (degrees)") plt.ylabel("Resolution (A^-1)") cbar = plt.colorbar() cbar.set_label("# shadowed reflections") plt.savefig("n_shadowed_hist2d.png") plt.clf() plt.scatter( hist_scan_angle.slot_centers().as_numpy_array(), hist_scan_angle.slots().as_numpy_array(), ) plt.xlabel("Scan angle (degrees)") plt.ylabel("# shadowed reflections") plt.savefig("n_shadowed_vs_scan_angle.png") plt.clf() plt.scatter(hist_res.slot_centers().as_numpy_array(), hist_res.slots().as_numpy_array()) plt.xlabel("d_star_sq") plt.savefig("n_shadowed_vs_resolution.png") plt.clf()