def create_multipanel_detector(offset, ncols=3, nrows=2): reference_detector = create_detector(offset) reference_panel = reference_detector[0] panel_npix = reference_panel.get_image_size() panel_npix = int(panel_npix[0] / ncols), int(panel_npix[1] / nrows) multi_panel_detector = Detector() for i in range(ncols): for j in range(nrows): origin_pix = i * panel_npix[0], j * panel_npix[1] origin = reference_panel.get_pixel_lab_coord(origin_pix) new_panel = Panel( reference_panel.get_type(), reference_panel.get_name() + str(j + i * nrows), reference_panel.get_fast_axis(), reference_panel.get_slow_axis(), origin, reference_panel.get_pixel_size(), panel_npix, reference_panel.get_trusted_range(), reference_panel.get_thickness(), reference_panel.get_material(), identifier=reference_panel.get_identifier(), ) multi_panel_detector.add_panel(new_panel) return multi_panel_detector
def get_optimized_detector(x, SIM): from dxtbx.model import Detector, Panel new_det = Detector() for pid in range(len(SIM.detector)): panel = SIM.detector[pid] panel_dict = panel.to_dict() group_id = SIM.panel_group_from_id[pid] if group_id in SIM.panel_groups_refined: Oang = x["group%d_RotOrth" % group_id].value Fang = x["group%d_RotFast" % group_id].value Sang = x["group%d_RotSlow" % group_id].value Xdist = x["group%d_ShiftX" % group_id].value Ydist = x["group%d_ShiftY" % group_id].value Zdist = x["group%d_ShiftZ" % group_id].value origin_of_rotation = SIM.panel_reference_from_id[pid] SIM.D.reference_origin = origin_of_rotation SIM.D.update_dxtbx_geoms(SIM.detector, SIM.beam.nanoBragg_constructor_beam, pid, Oang, Fang, Sang, Xdist, Ydist, Zdist, force=False) fdet = SIM.D.fdet_vector sdet = SIM.D.sdet_vector origin = SIM.D.get_origin() else: fdet = panel.get_fast_axis() sdet = panel.get_slow_axis() origin = panel.get_origin() panel_dict["fast_axis"] = fdet panel_dict["slow_axis"] = sdet panel_dict["origin"] = origin new_det.add_panel(Panel.from_dict(panel_dict)) return new_det
def get_multi_panel_eiger(detdist_mm=200, pixsize_mm=0.075, as_single_panel=False): """ :param detdist_mm: sample to detector in mm :param pixsize_mm: pix in mm :param as_single_panel: whether to return as just a large single panel camera :return: dxtbx detector """ # load a file specifying the gap positions # this is a 2D array # usually -1 is a gap, so you can create this array by doing # is_a_gap = np.array(eiger_image)==-1 (pseudo) is_a_gap = h5py.File("eiger_gaps.h5", "r")["is_a_gap"][()] # to view: #import pylab as plt #plt.imshow( is_a_gap) #plt.show() # its not perfect, some small regions are also flagged, we shall filter them though ydim, xdim = is_a_gap.shape center_x = xdim / 2. * pixsize_mm center_y = ydim / 2. * pixsize_mm master_panel_dict = { 'type': 'SENSOR_PAD', 'fast_axis': (1.0, 0.0, 0.0), 'slow_axis': (0.0, -1.0, 0.0), 'origin': (-center_x, center_y, -detdist_mm), 'raw_image_offset': (0, 0), 'image_size': (xdim, ydim), 'pixel_size': (pixsize_mm, pixsize_mm), # NOTE this will depend on your model 'trusted_range': (-1.0, 65535.0), 'thickness': 0.45, 'material': 'Si', 'mu': 3.969545947994824, 'identifier': '', 'mask': [], 'gain': 1.0, 'pedestal': 0.0, 'px_mm_strategy': { 'type': 'SimplePxMmStrategy' } } master_panel = Panel.from_dict(master_panel_dict) master_det = Detector() master_det.add_panel(master_panel) if as_single_panel: return master_det else: return panelize_eiger(master_det, is_a_gap)
def get_optimized_detector(x, ref_params, SIM): new_det = Detector() for pid in range(len(SIM.detector)): panel = SIM.detector[pid] panel_dict = panel.to_dict() group_id = SIM.panel_group_from_id[pid] if group_id in SIM.panel_groups_refined: Oang_p = ref_params["group%d_RotOrth" % group_id] Fang_p = ref_params["group%d_RotFast" % group_id] Sang_p = ref_params["group%d_RotSlow" % group_id] Xdist_p = ref_params["group%d_ShiftX" % group_id] Ydist_p = ref_params["group%d_ShiftY" % group_id] Zdist_p = ref_params["group%d_ShiftZ" % group_id] Oang = Oang_p.get_val(x[Oang_p.xpos]) Fang = Fang_p.get_val(x[Fang_p.xpos]) Sang = Sang_p.get_val(x[Sang_p.xpos]) Xdist = Xdist_p.get_val(x[Xdist_p.xpos]) Ydist = Ydist_p.get_val(x[Ydist_p.xpos]) Zdist = Zdist_p.get_val(x[Zdist_p.xpos]) origin_of_rotation = SIM.panel_reference_from_id[pid] SIM.D.reference_origin = origin_of_rotation SIM.D.update_dxtbx_geoms(SIM.detector, SIM.beam.nanoBragg_constructor_beam, pid, Oang, Fang, Sang, Xdist, Ydist, Zdist, force=False) fdet = SIM.D.fdet_vector sdet = SIM.D.sdet_vector origin = SIM.D.get_origin() else: fdet = panel.get_fast_axis() sdet = panel.get_slow_axis() origin = panel.get_origin() panel_dict["fast_axis"] = fdet panel_dict["slow_axis"] = sdet panel_dict["origin"] = origin new_det.add_panel(Panel.from_dict(panel_dict)) return new_det
def __call__(self): from dxtbx.model import Detector, Panel # import dependency d1 = Detector() p = d1.add_panel() p.set_name("p1") p.set_type("panel") p.set_pixel_size((0.1, 0.1)) p.set_image_size((100, 100)) p.set_trusted_range((0, 1000)) p.set_local_frame((1, 0, 0), (0, 1, 0), (0, 0, 1)) p = d1.add_panel() p.set_name("p2") p.set_type("panel") p.set_pixel_size((0.2, 0.2)) p.set_image_size((200, 200)) p.set_trusted_range((0, 2000)) p.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 1)) root = d1.hierarchy() g = root.add_group() g.set_name("g1") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 2)) g.add_panel(d1[0]) g = root.add_group() g.set_name("g2") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 4)) g.add_panel(d1[1]) d = d1.to_dict() d2 = Detector.from_dict(d) assert (len(d1) == len(d2)) for p1, p2 in zip(d1, d2): assert (p1 == p2) assert (d1.hierarchy() == d2.hierarchy()) assert (d1 == d2) print 'OK'
def __call__(self): from dxtbx.model import Detector, Panel # import dependency d1 = Detector() p = d1.add_panel() p.set_name("p1") p.set_type("panel") p.set_pixel_size((0.1, 0.1)) p.set_image_size((100, 100)) p.set_trusted_range((0, 1000)) p.set_local_frame((1, 0, 0), (0, 1, 0), (0, 0, 1)) p = d1.add_panel() p.set_name("p2") p.set_type("panel") p.set_pixel_size((0.2, 0.2)) p.set_image_size((200, 200)) p.set_trusted_range((0, 2000)) p.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 1)) root = d1.hierarchy() g = root.add_group() g.set_name("g1") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 2)) g.add_panel(d1[0]) g = root.add_group() g.set_name("g2") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 4)) g.add_panel(d1[1]) d = d1.to_dict() d2 = Detector.from_dict(d) assert(len(d1) == len(d2)) for p1, p2 in zip(d1, d2): assert(p1 == p2) assert(d1.hierarchy() == d2.hierarchy()) assert(d1 == d2) print 'OK'
def downsample_detector(det, downsamp=1): newD = Detector() for panel in det: fast = panel.get_fast_axis() slow = panel.get_slow_axis() orig = panel.get_origin() panel_dict = panel.to_dict() panel_dict['fast'] = fast panel_dict['slow'] = slow panel_dict['origin'] = orig fast_dim, slow_dim = panel.get_image_size() panel_dict['image_size'] = int(fast_dim / float(downsamp)), int( slow_dim / float(downsamp)) pixsize = panel.get_pixel_size()[0] panel_dict['pixel_size'] = pixsize * downsamp, pixsize * downsamp newpanel = Panel.from_dict(panel_dict) newD.add_panel(newpanel) return newD
def create_kapton_face(ori, fast, slow, image_size, pixel_size, name): """Create a face of the kapton as a dxtbx detector object""" from dxtbx.model import Detector d = Detector() p = d.add_panel() p.set_local_frame(fast.elems, slow.elems, ori.elems) p.set_pixel_size((pixel_size, pixel_size)) p.set_image_size(image_size) p.set_trusted_range((-1, 2e6)) p.set_name("KAPTON_%s" % name) return d
def make_multi_panel(single_panel_detector): """Create a 3x3 multi-panel detector filling the same space as a supplied single panel detector""" from dials.test.algorithms.refinement.tst_multi_panel_detector_parameterisation \ import make_panel_in_array from dials.test.algorithms.refinement.setup_geometry import \ random_vector_close_to multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array( (x, y), single_panel_detector[0]) multi_panel_detector.add_panel(new_panel) # apply small random shifts & rotations to each panel for p in multi_panel_detector: # perturb origin vector o_multiplier = random.gauss(1.0, 0.01) new_origin = random_vector_close_to(p.get_origin(), sd=0.1) new_origin *= o_multiplier # perturb fast direction vector new_dir1 = random_vector_close_to(p.get_fast_axis(), sd=0.5) # create vector in the plane of dir1-dir2 dir1_dir2 = random_vector_close_to(p.get_slow_axis(), sd=0.5) # find normal to panel plane and thus new slow direction vector dn = new_dir1.cross(dir1_dir2) new_dir2 = dn.cross(new_dir1) # set panel frame p.set_frame(new_dir1, new_dir2, new_origin) return multi_panel_detector
def test_detector(): d1 = Detector() p = d1.add_panel() p.set_name("p1") p.set_type("panel") p.set_pixel_size((0.1, 0.1)) p.set_image_size((100, 100)) p.set_trusted_range((0, 1000)) p.set_local_frame((1, 0, 0), (0, 1, 0), (0, 0, 1)) p = d1.add_panel() p.set_name("p2") p.set_type("panel") p.set_pixel_size((0.2, 0.2)) p.set_image_size((200, 200)) p.set_trusted_range((0, 2000)) p.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 1)) root = d1.hierarchy() g = root.add_group() g.set_name("g1") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 2)) g.add_panel(d1[0]) g = root.add_group() g.set_name("g2") g.set_type("group") g.set_local_frame((0, 1, 0), (1, 0, 0), (0, 0, 4)) g.add_panel(d1[1]) d = d1.to_dict() d2 = Detector.from_dict(d) assert len(d1) == len(d2) for p1, p2 in zip(d1, d2): assert p1 == p2 assert d1.hierarchy() == d2.hierarchy() assert d1 == d2
def panelize_eiger(eiger_dxtbx_model, is_a_gap): """ :param eiger_dxtbx_model: dxtbx geometry for the eiger (single node model) :param is_a_gap: a 2D numpy array the same shape as the eiger image (4371, 4150) True means the pixel is a gap, False means the pixel is a mask Make this by loading an eiger image and selecting all pixels that are equal to -1 is_a_gap = np.array(eiger_image)==-1 :return: dxtbx multi panel model for eiger 16M """ multiEiger = Detector() # we will make a new detector where wach panel has its own origin, but all share the same fast,slow scan directions detector_origin = np.array(eiger_dxtbx_model[0].get_origin()) F = np.array(eiger_dxtbx_model[0].get_fast_axis()) S = np.array(eiger_dxtbx_model[0].get_slow_axis()) pixsize = eiger_dxtbx_model[0].get_pixel_size()[0] panel_dict = eiger_dxtbx_model[0].to_dict() is_a_panel = np.logical_not(is_a_gap) labs, nlabs = label(is_a_panel) for i in range(nlabs + 1): region = labs == i npixels = np.sum(region) # there might be some other connected regions that are not panels (bad regions for example) if 200000 < npixels < 530000: # panel size is 529420 pixels, I think ypos, xpos = np.where(region) jmin, imin = min(ypos), min( xpos) # location of first pixel in the region jmax, imax = max(ypos), max(xpos) panel_shape = (int(imax - imin), int(jmax - jmin)) panel_origin = detector_origin + F * imin * pixsize + S * jmin * pixsize panel_dict["origin"] = tuple(panel_origin) panel_dict["image_size"] = panel_shape panel = Panel.from_dict(panel_dict) multiEiger.add_panel(panel) return multiEiger
def test_check_and_remove(): test = _Test() # Override the single panel model and parameterisation. This test function # exercises the code for non-hierarchical multi-panel detectors. The # hierarchical detector version is tested via test_cspad_refinement.py multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array((x, y), test.detector[0]) multi_panel_detector.add_panel(new_panel) test.detector = multi_panel_detector test.stills_experiments[0].detector = multi_panel_detector test.det_param = DetectorParameterisationMultiPanel(multi_panel_detector, test.beam) # update the generated reflections test.generate_reflections() # Predict the reflections in place and put in a reflection manager ref_predictor = StillsExperimentsPredictor(test.stills_experiments) ref_predictor(test.reflections) test.refman = ReflectionManagerFactory.from_parameters_reflections_experiments( refman_phil_scope.extract(), test.reflections, test.stills_experiments, do_stills=True, ) test.refman.finalise() # Build a prediction parameterisation for the stills experiment test.pred_param = StillsPredictionParameterisation( test.stills_experiments, detector_parameterisations=[test.det_param], beam_parameterisations=[test.s0_param], xl_orientation_parameterisations=[test.xlo_param], xl_unit_cell_parameterisations=[test.xluc_param], ) # A non-hierarchical detector does not have panel groups, thus panels are # not treated independently wrt which reflections affect their parameters. # As before, setting 792 reflections as the minimum should leave all # parameters free, and should not remove any reflections options = ar_phil_scope.extract() options.min_nref_per_parameter = 792 ar = AutoReduce(options, pred_param=test.pred_param, reflection_manager=test.refman) ar.check_and_remove() det_params = test.pred_param.get_detector_parameterisations() beam_params = test.pred_param.get_beam_parameterisations() xl_ori_params = test.pred_param.get_crystal_orientation_parameterisations() xl_uc_params = test.pred_param.get_crystal_unit_cell_parameterisations() assert det_params[0].num_free() == 6 assert beam_params[0].num_free() == 3 assert xl_ori_params[0].num_free() == 3 assert xl_uc_params[0].num_free() == 6 assert len(test.refman.get_obs()) == 823 # Setting 793 reflections as the minimum fixes 3 unit cell parameters, # and removes all those reflections. There are then too few reflections # for any parameterisation and all will be fixed, leaving no free # parameters for refinement. This fails within PredictionParameterisation, # during update so the final 31 reflections are not removed. options = ar_phil_scope.extract() options.min_nref_per_parameter = 793 ar = AutoReduce(options, pred_param=test.pred_param, reflection_manager=test.refman) with pytest.raises( DialsRefineConfigError, match="There are no free parameters for refinement" ): ar.check_and_remove() det_params = test.pred_param.get_detector_parameterisations() beam_params = test.pred_param.get_beam_parameterisations() xl_ori_params = test.pred_param.get_crystal_orientation_parameterisations() xl_uc_params = test.pred_param.get_crystal_unit_cell_parameterisations() assert det_params[0].num_free() == 0 assert beam_params[0].num_free() == 0 assert xl_ori_params[0].num_free() == 0 assert xl_uc_params[0].num_free() == 0 assert len(test.refman.get_obs()) == 823 - 792
def _detector(self): """The _detector() function returns a model for a CSPAD detector as used at LCLS's CXI and XPP endstations. It converts the metrology information in the pure Python object extracted from the image pickle to DXTBX-style transformation vectors. Only ASIC:s are considered, since DXTBX metrology is not concerned with hierarchies. Merged from xfel.cftbx.detector.cspad_detector.readHeader() and xfel.cftbx.detector.metrology.metrology_as_dxtbx_vectors(). """ from dxtbx.model import SimplePxMmStrategy from dxtbx.model import Detector from scitbx.matrix import col # XXX Introduces dependency on cctbx.xfel! Should probably be # merged into the code here! from xfel.cftbx.detector.metrology import _transform, get_projection_matrix # Apply the detector distance to the translation of the root # detector object. d = self._metrology_params.detector Tb_d = _transform( col(d.orientation).normalize(), col(d.translation) + col((0, 0, -self._metrology_params.distance * 1e-3)), )[1] self._raw_data = [] detector = Detector() for p in d.panel: Tb_p = ( Tb_d * _transform(col(p.orientation).normalize(), col(p.translation))[1] ) for s in p.sensor: Tb_s = ( Tb_p * _transform(col(s.orientation).normalize(), col(s.translation))[1] ) for a in s.asic: Tb_a = ( Tb_s * _transform( col(a.orientation).normalize(), col(a.translation) )[1] ) Pb = get_projection_matrix(a.pixel_size, a.dimension)[1] # The DXTBX-style metrology description consists of three # vectors for each ASIC. The origin vector locates the # (0, 0)-pixel in the laboratory frame in units of mm. # The second and third vectors give the directions to the # pixels immediately next to (0, 0) in the fast and slow # directions, respectively, in arbitrary units. origin = Tb_a * Pb * col((0, 0, 1)) fast = Tb_a * Pb * col((0, a.dimension[0], 1)) - origin slow = Tb_a * Pb * col((a.dimension[1], 0, 1)) - origin # Convert vector units from meter to millimeter. The # default, SimplePxMmStrategy applies here. XXX Due to # dark subtraction, a valid pixel intensity may be # negative, and this is currently not reflected by # trusted_range. key = (d.serial, p.serial, s.serial, a.serial) panel = detector.add_panel() panel.set_type("PAD") panel.set_name("%d:%d:%d:%d" % key) panel.set_local_frame( [t * 1e3 for t in fast.elems[0:3]], [t * 1e3 for t in slow.elems[0:3]], [t * 1e3 for t in origin.elems[0:3]], ) panel.set_pixel_size([t * 1e3 for t in a.pixel_size]) panel.set_image_size(a.dimension) panel.set_trusted_range((0, a.saturation)) self._raw_data.append(self._tiles[key]) return detector
class DetectorFactory(object): ''' A class to create a detector model from NXmx stuff ''' def __init__(self, obj, beam): from dxtbx.model import Detector, Panel from cctbx.eltbx import attenuation_coefficient from dxtbx.model import ParallaxCorrectedPxMmStrategy from scitbx import matrix # Get the handles nx_file = obj.handle.file nx_detector = obj.handle nx_module = obj.modules[0].handle # Get the detector name and type detector_type = str(nx_detector['type'][()]) detector_name = str(nx_detector.name) # Get the trusted range of pixel values trusted_range = (-1, float(nx_detector['saturation_value'][()])) # Get the detector thickness thickness = nx_detector['sensor_thickness'] thickness_value = float(thickness[()]) thickness_units = thickness.attrs['units'] thickness_value = float(convert_units( thickness_value, thickness_units, "mm")) # Get the detector material material = str(nx_detector['sensor_material'][()]) # Get the fast pixel size and vector fast_pixel_direction = nx_module['fast_pixel_direction'] fast_pixel_direction_value = float(fast_pixel_direction[()]) fast_pixel_direction_units = fast_pixel_direction.attrs['units'] fast_pixel_direction_vector = fast_pixel_direction.attrs['vector'] fast_pixel_direction_value = convert_units( fast_pixel_direction_value, fast_pixel_direction_units, "mm") fast_axis = matrix.col(fast_pixel_direction_vector).normalize() # Get the slow pixel size and vector slow_pixel_direction = nx_module['slow_pixel_direction'] slow_pixel_direction_value = float(slow_pixel_direction[()]) slow_pixel_direction_units = slow_pixel_direction.attrs['units'] slow_pixel_direction_vector = slow_pixel_direction.attrs['vector'] slow_pixel_direction_value = convert_units( slow_pixel_direction_value, slow_pixel_direction_units, "mm") slow_axis = matrix.col(slow_pixel_direction_vector).normalize() # Get the origin vector module_offset = nx_module['module_offset'] origin = construct_vector( nx_file, module_offset.name) # Ensure that fast and slow axis are orthogonal normal = fast_axis.cross(slow_axis) slow_axis = -fast_axis.cross(normal) # Compute the attenuation coefficient. # This will fail for undefined composite materials # mu_at_angstrom returns cm^-1, but need mu in mm^-1 if material == 'Si': pass elif material == 'Silicon': material = 'Si' elif material == 'Sillicon': material = 'Si' elif material == 'CdTe': pass elif material == 'GaAs': pass else: raise RuntimeError('Unknown material: %s' % material) table = attenuation_coefficient.get_table(material) wavelength = beam.get_wavelength() mu = table.mu_at_angstrom(wavelength) / 10.0 # Construct the detector model pixel_size = (fast_pixel_direction_value, slow_pixel_direction_value) image_size = tuple(map(int, nx_module['data_size'])) self.model = Detector() self.model.add_panel( Panel( detector_type, detector_name, tuple(fast_axis), tuple(slow_axis), tuple(origin), pixel_size, image_size, trusted_range, thickness_value, material, mu)) # Set the parallax correction for panel in self.model: panel.set_px_mm_strategy(ParallaxCorrectedPxMmStrategy(mu, thickness_value)) panel.set_type('SENSOR_PAD')
def test(args=[]): ############################# # Setup experimental models # ############################# master_phil = parse( """ include scope dials.test.algorithms.refinement.geometry_phil include scope dials.test.algorithms.refinement.minimiser_phil """, process_includes=True, ) models = setup_geometry.Extract(master_phil, cmdline_args=args) single_panel_detector = models.detector mygonio = models.goniometer mycrystal = models.crystal mybeam = models.beam # Make a 3x3 multi panel detector filling the same space as the existing # single panel detector. Each panel of the multi-panel detector has pixels with # 1/3 the length dimensions of the single panel. multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array((x, y), single_panel_detector[0]) multi_panel_detector.add_panel(new_panel) # Build a mock scan for a 180 degree sweep sf = ScanFactory() myscan = sf.make_scan( image_range=(1, 1800), exposure_times=0.1, oscillation=(0, 0.1), epochs=list(range(1800)), deg=True, ) sweep_range = myscan.get_oscillation_range(deg=False) im_width = myscan.get_oscillation(deg=False)[1] assert sweep_range == (0.0, pi) assert approx_equal(im_width, 0.1 * pi / 180.0) # Build ExperimentLists experiments_single_panel = ExperimentList() experiments_multi_panel = ExperimentList() experiments_single_panel.append( Experiment( beam=mybeam, detector=single_panel_detector, goniometer=mygonio, scan=myscan, crystal=mycrystal, imageset=None, )) experiments_multi_panel.append( Experiment( beam=mybeam, detector=multi_panel_detector, goniometer=mygonio, scan=myscan, crystal=mycrystal, imageset=None, )) ########################### # Parameterise the models # ########################### det_param = DetectorParameterisationSinglePanel(single_panel_detector) s0_param = BeamParameterisation(mybeam, mygonio) xlo_param = CrystalOrientationParameterisation(mycrystal) xluc_param = CrystalUnitCellParameterisation(mycrystal) multi_det_param = DetectorParameterisationMultiPanel( multi_panel_detector, mybeam) # Fix beam to the X-Z plane (imgCIF geometry), fix wavelength s0_param.set_fixed([True, False, True]) # Fix crystal parameters # xluc_param.set_fixed([True, True, True, True, True, True]) ######################################################################## # Link model parameterisations together into a parameterisation of the # # prediction equation # ######################################################################## pred_param = XYPhiPredictionParameterisation(experiments_single_panel, [det_param], [s0_param], [xlo_param], [xluc_param]) pred_param2 = XYPhiPredictionParameterisation( experiments_multi_panel, [multi_det_param], [s0_param], [xlo_param], [xluc_param], ) ################################ # Apply known parameter shifts # ################################ # shift detectors by 1.0 mm each translation and 2 mrad each rotation det_p_vals = det_param.get_param_vals() p_vals = [ a + b for a, b in zip(det_p_vals, [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]) ] det_param.set_param_vals(p_vals) multi_det_p_vals = multi_det_param.get_param_vals() p_vals = [ a + b for a, b in zip(multi_det_p_vals, [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]) ] multi_det_param.set_param_vals(p_vals) # shift beam by 2 mrad in free axis s0_p_vals = s0_param.get_param_vals() p_vals = list(s0_p_vals) p_vals[0] += 2.0 s0_param.set_param_vals(p_vals) # rotate crystal a bit (=2 mrad each rotation) xlo_p_vals = xlo_param.get_param_vals() p_vals = [a + b for a, b in zip(xlo_p_vals, [2.0, 2.0, 2.0])] xlo_param.set_param_vals(p_vals) # change unit cell a bit (=0.1 Angstrom length upsets, 0.1 degree of # gamma angle) xluc_p_vals = xluc_param.get_param_vals() cell_params = mycrystal.get_unit_cell().parameters() cell_params = [ a + b for a, b in zip(cell_params, [0.1, 0.1, 0.1, 0.0, 0.0, 0.1]) ] new_uc = unit_cell(cell_params) newB = matrix.sqr(new_uc.fractionalization_matrix()).transpose() S = symmetrize_reduce_enlarge(mycrystal.get_space_group()) S.set_orientation(orientation=newB) X = tuple([e * 1.0e5 for e in S.forward_independent_parameters()]) xluc_param.set_param_vals(X) ############################# # Generate some reflections # ############################# # All indices in a 2.0 Angstrom sphere resolution = 2.0 index_generator = IndexGenerator( mycrystal.get_unit_cell(), space_group(space_group_symbols(1).hall()).type(), resolution, ) indices = index_generator.to_array() # for the reflection predictor, it doesn't matter which experiment list is # passed, as the detector is not used ref_predictor = ScansRayPredictor(experiments_single_panel, sweep_range) # get two sets of identical reflections obs_refs = ref_predictor(indices) obs_refs2 = ref_predictor(indices) for r1, r2 in zip(obs_refs, obs_refs2): assert r1["s1"] == r2["s1"] # get the panel intersections sel = ray_intersection(single_panel_detector, obs_refs) obs_refs = obs_refs.select(sel) sel = ray_intersection(multi_panel_detector, obs_refs2) obs_refs2 = obs_refs2.select(sel) assert len(obs_refs) == len(obs_refs2) # Set 'observed' centroids from the predicted ones obs_refs["xyzobs.mm.value"] = obs_refs["xyzcal.mm"] obs_refs2["xyzobs.mm.value"] = obs_refs2["xyzcal.mm"] # Invent some variances for the centroid positions of the simulated data im_width = 0.1 * pi / 180.0 px_size = single_panel_detector[0].get_pixel_size() var_x = flex.double(len(obs_refs), (px_size[0] / 2.0)**2) var_y = flex.double(len(obs_refs), (px_size[1] / 2.0)**2) var_phi = flex.double(len(obs_refs), (im_width / 2.0)**2) # set the variances and frame numbers obs_refs["xyzobs.mm.variance"] = flex.vec3_double(var_x, var_y, var_phi) obs_refs2["xyzobs.mm.variance"] = flex.vec3_double(var_x, var_y, var_phi) # Add in flags and ID columns by copying into standard reflection tables tmp = flex.reflection_table.empty_standard(len(obs_refs)) tmp.update(obs_refs) obs_refs = tmp tmp = flex.reflection_table.empty_standard(len(obs_refs2)) tmp.update(obs_refs2) obs_refs2 = tmp ############################### # Undo known parameter shifts # ############################### s0_param.set_param_vals(s0_p_vals) det_param.set_param_vals(det_p_vals) multi_det_param.set_param_vals(det_p_vals) xlo_param.set_param_vals(xlo_p_vals) xluc_param.set_param_vals(xluc_p_vals) ##################################### # Select reflections for refinement # ##################################### refman = ReflectionManager(obs_refs, experiments_single_panel) refman2 = ReflectionManager(obs_refs, experiments_multi_panel) ############################### # Set up the target functions # ############################### mytarget = LeastSquaresPositionalResidualWithRmsdCutoff( experiments_single_panel, ScansExperimentsPredictor(experiments_single_panel), refman, pred_param, restraints_parameterisation=None, ) mytarget2 = LeastSquaresPositionalResidualWithRmsdCutoff( experiments_multi_panel, ScansExperimentsPredictor(experiments_multi_panel), refman2, pred_param2, restraints_parameterisation=None, ) ################################# # Set up the refinement engines # ################################# refiner = setup_minimiser.Extract(master_phil, mytarget, pred_param, cmdline_args=args).refiner refiner2 = setup_minimiser.Extract(master_phil, mytarget2, pred_param2, cmdline_args=args).refiner refiner.run() # reset parameters and run refinement with the multi panel detector s0_param.set_param_vals(s0_p_vals) multi_det_param.set_param_vals(det_p_vals) xlo_param.set_param_vals(xlo_p_vals) xluc_param.set_param_vals(xluc_p_vals) refiner2.run() # same number of steps assert refiner.get_num_steps() == refiner2.get_num_steps() # same rmsds for rmsd, rmsd2 in zip(refiner.history["rmsd"], refiner2.history["rmsd"]): assert approx_equal(rmsd, rmsd2) # same parameter values each step for params, params2 in zip(refiner.history["parameter_vector"], refiner.history["parameter_vector"]): assert approx_equal(params, params2)
from __future__ import print_function from dxtbx.model import Detector d = Detector() p1 = d.add_panel() p2 = d.add_panel() p3 = d.add_panel() p4 = d.add_panel() root = d.hierarchy() g = root.add_group() g.add_panel(d[0]) g.add_panel(d[1]) root.add_panel(d[2]) root.add_panel(d[3]) print(d.to_dict())
def test_check_and_remove(): test = _Test() # Override the single panel model and parameterisation. This test function # exercises the code for non-hierarchical multi-panel detectors. The # hierarchical detector version is tested via test_cspad_refinement.py from dxtbx.model import Detector from dials.algorithms.refinement.parameterisation.detector_parameters import ( DetectorParameterisationMultiPanel, ) from dials.test.algorithms.refinement.test_multi_panel_detector_parameterisation import ( make_panel_in_array, ) multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array((x, y), test.detector[0]) multi_panel_detector.add_panel(new_panel) test.detector = multi_panel_detector test.stills_experiments[0].detector = multi_panel_detector test.det_param = DetectorParameterisationMultiPanel( multi_panel_detector, test.beam) # update the generated reflections test.generate_reflections() # Predict the reflections in place and put in a reflection manager ref_predictor = StillsExperimentsPredictor(test.stills_experiments) ref_predictor(test.reflections) test.refman = ReflectionManagerFactory.from_parameters_reflections_experiments( refman_phil_scope.extract(), test.reflections, test.stills_experiments, do_stills=True, ) test.refman.finalise() # A non-hierarchical detector does not have panel groups, thus panels are # not treated independently wrt which reflections affect their parameters. # As before, setting 137 reflections as the minimum should leave all # parameters free, and should not remove any reflections options = ar_phil_scope.extract() options.min_nref_per_parameter = 137 ar = AutoReduce( options, [test.det_param], [test.s0_param], [test.xlo_param], [test.xluc_param], gon_params=[], reflection_manager=test.refman, ) ar.check_and_remove() assert ar.det_params[0].num_free() == 6 assert ar.beam_params[0].num_free() == 3 assert ar.xl_ori_params[0].num_free() == 3 assert ar.xl_uc_params[0].num_free() == 6 assert len(ar.reflection_manager.get_obs()) == 823 # Setting reflections as the minimum should fix the detector parameters, # which removes that parameterisation. Because all reflections are recorded # on that detector, they will all be removed as well. This then affects all # other parameterisations, which will be removed. options = ar_phil_scope.extract() options.min_nref_per_parameter = 138 ar = AutoReduce( options, [test.det_param], [test.s0_param], [test.xlo_param], [test.xluc_param], gon_params=[], reflection_manager=test.refman, ) ar.check_and_remove() assert not ar.det_params assert not ar.beam_params assert not ar.xl_ori_params assert not ar.xl_uc_params assert len(ar.reflection_manager.get_obs()) == 0
def init_test(): models = setup_geometry.Extract(master_phil) single_panel_detector = models.detector gonio = models.goniometer crystal = models.crystal beam = models.beam # Make a 3x3 multi panel detector filling the same space as the existing # single panel detector. Each panel of the multi-panel detector has pixels # with 1/3 the length dimensions of the single panel. multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array((x, y), single_panel_detector[0]) multi_panel_detector.add_panel(new_panel) # Build a mock scan for a 180 degree sequence sf = ScanFactory() scan = sf.make_scan( image_range=(1, 1800), exposure_times=0.1, oscillation=(0, 0.1), epochs=list(range(1800)), deg=True, ) sequence_range = scan.get_oscillation_range(deg=False) im_width = scan.get_oscillation(deg=False)[1] assert sequence_range == (0.0, pi) assert approx_equal(im_width, 0.1 * pi / 180.0) # Build ExperimentLists experiments_single_panel = ExperimentList() experiments_multi_panel = ExperimentList() experiments_single_panel.append( Experiment( beam=beam, detector=single_panel_detector, goniometer=gonio, scan=scan, crystal=crystal, imageset=None, ) ) experiments_multi_panel.append( Experiment( beam=beam, detector=multi_panel_detector, goniometer=gonio, scan=scan, crystal=crystal, imageset=None, ) ) # Generate some reflections # All indices in a 2.0 Angstrom sphere resolution = 2.0 index_generator = IndexGenerator( crystal.get_unit_cell(), space_group(space_group_symbols(1).hall()).type(), resolution, ) indices = index_generator.to_array() # for the reflection predictor, it doesn't matter which experiment list is # passed, as the detector is not used ref_predictor = ScansRayPredictor( experiments_single_panel, scan.get_oscillation_range(deg=False) ) # get two sets of identical reflections obs_refs_single = ref_predictor(indices) obs_refs_multi = ref_predictor(indices) for r1, r2 in zip(obs_refs_single.rows(), obs_refs_multi.rows()): assert r1["s1"] == r2["s1"] # get the panel intersections sel = ray_intersection(single_panel_detector, obs_refs_single) obs_refs_single = obs_refs_single.select(sel) sel = ray_intersection(multi_panel_detector, obs_refs_multi) obs_refs_multi = obs_refs_multi.select(sel) assert len(obs_refs_single) == len(obs_refs_multi) # Set 'observed' centroids from the predicted ones obs_refs_single["xyzobs.mm.value"] = obs_refs_single["xyzcal.mm"] obs_refs_multi["xyzobs.mm.value"] = obs_refs_multi["xyzcal.mm"] # Invent some variances for the centroid positions of the simulated data im_width = 0.1 * pi / 180.0 px_size = single_panel_detector[0].get_pixel_size() var_x = flex.double(len(obs_refs_single), (px_size[0] / 2.0) ** 2) var_y = flex.double(len(obs_refs_single), (px_size[1] / 2.0) ** 2) var_phi = flex.double(len(obs_refs_single), (im_width / 2.0) ** 2) # set the variances and frame numbers obs_refs_single["xyzobs.mm.variance"] = flex.vec3_double(var_x, var_y, var_phi) obs_refs_multi["xyzobs.mm.variance"] = flex.vec3_double(var_x, var_y, var_phi) # Add in flags and ID columns by copying into standard reflection tables tmp = flex.reflection_table.empty_standard(len(obs_refs_single)) tmp.update(obs_refs_single) obs_refs_single = tmp tmp = flex.reflection_table.empty_standard(len(obs_refs_multi)) tmp.update(obs_refs_multi) obs_refs_multi = tmp test_data = namedtuple( "test_data", [ "experiments_single_panel", "experiments_multi_panel", "observations_single_panel", "observations_multi_panel", ], ) return test_data( experiments_single_panel, experiments_multi_panel, obs_refs_single, obs_refs_multi, )
models = setup_geometry.Extract(master_phil, cmdline_args = args) single_panel_detector = models.detector mygonio = models.goniometer mycrystal = models.crystal mybeam = models.beam # Make a 3x3 multi panel detector filling the same space as the existing # single panel detector. Each panel of the multi-panel detector has pixels with # 1/3 the length dimensions of the single panel. multi_panel_detector = Detector() for x in range(3): for y in range(3): new_panel = make_panel_in_array((x, y), single_panel_detector[0]) multi_panel_detector.add_panel(new_panel) # Build a mock scan for a 180 degree sweep sf = ScanFactory() myscan = sf.make_scan(image_range = (1,1800), exposure_times = 0.1, oscillation = (0, 0.1), epochs = range(1800), deg = True) sweep_range = myscan.get_oscillation_range(deg=False) im_width = myscan.get_oscillation(deg=False)[1] assert sweep_range == (0., pi) assert approx_equal(im_width, 0.1 * pi / 180.) # Build ExperimentLists experiments_single_panel = ExperimentList()
def _detector(self): '''The _detector() function returns a model for a CSPAD detector as used at LCLS's CXI and XPP endstations. It converts the metrology information in the pure Python object extracted from the image pickle to DXTBX-style transformation vectors. Only ASIC:s are considered, since DXTBX metrology is not concerned with hierarchies. Merged from xfel.cftbx.detector.cspad_detector.readHeader() and xfel.cftbx.detector.metrology.metrology_as_dxtbx_vectors(). ''' from dxtbx.model import SimplePxMmStrategy from dxtbx.model import Detector from scitbx.matrix import col # XXX Introduces dependency on cctbx.xfel! Should probably be # merged into the code here! from xfel.cftbx.detector.metrology import \ _transform, get_projection_matrix # Apply the detector distance to the translation of the root # detector object. d = self._metrology_params.detector Tb_d = _transform( col(d.orientation).normalize(), col(d.translation) + col((0, 0, -self._metrology_params.distance * 1e-3)))[1] self._raw_data = [] detector = Detector() for p in d.panel: Tb_p = Tb_d * _transform( col(p.orientation).normalize(), col(p.translation))[1] for s in p.sensor: Tb_s = Tb_p * _transform( col(s.orientation).normalize(), col(s.translation))[1] for a in s.asic: Tb_a = Tb_s * _transform( col(a.orientation).normalize(), col(a.translation))[1] Pb = get_projection_matrix(a.pixel_size, a.dimension)[1] # The DXTBX-style metrology description consists of three # vectors for each ASIC. The origin vector locates the # (0, 0)-pixel in the laboratory frame in units of mm. # The second and third vectors give the directions to the # pixels immediately next to (0, 0) in the fast and slow # directions, respectively, in arbitrary units. origin = Tb_a * Pb * col((0, 0, 1)) fast = Tb_a * Pb * col((0, a.dimension[0], 1)) - origin slow = Tb_a * Pb * col((a.dimension[1], 0, 1)) - origin # Convert vector units from meter to millimeter. The # default, SimplePxMmStrategy applies here. XXX Due to # dark subtraction, a valid pixel intensity may be # negative, and this is currently not reflected by # trusted_range. key = (d.serial, p.serial, s.serial, a.serial) panel = detector.add_panel() panel.set_type("PAD") panel.set_name('%d:%d:%d:%d' % key) panel.set_local_frame( [t * 1e3 for t in fast.elems[0:3]], [t * 1e3 for t in slow.elems[0:3]], [t * 1e3 for t in origin.elems[0:3]]) panel.set_pixel_size([t * 1e3 for t in a.pixel_size]) panel.set_image_size(a.dimension) panel.set_trusted_range((0, a.saturation)) self._raw_data.append(self._tiles[key]) return detector
from dxtbx.model import Detector d = Detector() p1 = d.add_panel() p2 = d.add_panel() p3 = d.add_panel() p4 = d.add_panel() root = d.hierarchy() g = root.add_group() g.add_panel(d[0]) g.add_panel(d[1]) root.add_panel(d[2]) root.add_panel(d[3]) print d.to_dict()
def update_detector(x, ref_params, SIM, save=None): """ Update the internal geometry of the diffBragg instance :param x: refinement parameters as seen by scipy.optimize (e.g. rescaled floats) :param ref_params: diffBragg.refiners.Parameters (dict of RangedParameters) :param SIM: SIM instance (instance of nanoBragg.sim_data.SimData) :param save: optional name to save the detector """ det = SIM.detector if save is not None: new_det = Detector() for pid in range(len(det)): panel = det[pid] panel_dict = panel.to_dict() group_id = SIM.panel_group_from_id[pid] if group_id not in SIM.panel_groups_refined: fdet = panel.get_fast_axis() sdet = panel.get_slow_axis() origin = panel.get_origin() else: Oang_p = ref_params["group%d_RotOrth" % group_id] Fang_p = ref_params["group%d_RotFast" % group_id] Sang_p = ref_params["group%d_RotSlow" % group_id] Xdist_p = ref_params["group%d_ShiftX" % group_id] Ydist_p = ref_params["group%d_ShiftY" % group_id] Zdist_p = ref_params["group%d_ShiftZ" % group_id] Oang = Oang_p.get_val(x[Oang_p.xpos]) Fang = Fang_p.get_val(x[Fang_p.xpos]) Sang = Sang_p.get_val(x[Sang_p.xpos]) Xdist = Xdist_p.get_val(x[Xdist_p.xpos]) Ydist = Ydist_p.get_val(x[Ydist_p.xpos]) Zdist = Zdist_p.get_val(x[Zdist_p.xpos]) origin_of_rotation = SIM.panel_reference_from_id[pid] SIM.D.reference_origin = origin_of_rotation SIM.D.update_dxtbx_geoms(det, SIM.beam.nanoBragg_constructor_beam, pid, Oang, Fang, Sang, Xdist, Ydist, Zdist, force=False) fdet = SIM.D.fdet_vector sdet = SIM.D.sdet_vector origin = SIM.D.get_origin() if save is not None: panel_dict["fast_axis"] = fdet panel_dict["slow_axis"] = sdet panel_dict["origin"] = origin new_det.add_panel(Panel.from_dict(panel_dict)) if save is not None and COMM.rank == 0: t = time.time() El = ExperimentList() E = Experiment() E.detector = new_det El.append(E) El.as_file(save) t = time.time() - t print("Saved detector model to %s (took %.4f sec)" % (save, t), flush=True)
class DetectorFactory(object): ''' A class to create a detector model from NXmx stuff ''' def __init__(self, obj, beam): from dxtbx.model import Detector, Panel from cctbx.eltbx import attenuation_coefficient from dxtbx.model import ParallaxCorrectedPxMmStrategy from scitbx import matrix # Get the handles nx_file = obj.handle.file nx_detector = obj.handle nx_module = obj.modules[0].handle # Get the detector name and type detector_type = str(nx_detector['type'][()]) detector_name = str(nx_detector.name) # Get the trusted range of pixel values trusted_range = (-1, float(nx_detector['saturation_value'][()])) # Get the detector thickness thickness = nx_detector['sensor_thickness'] thickness_value = float(thickness[()]) thickness_units = thickness.attrs['units'] thickness_value = float( convert_units(thickness_value, thickness_units, "mm")) # Get the detector material material = str(nx_detector['sensor_material'][()]) # Get the fast pixel size and vector fast_pixel_direction = nx_module['fast_pixel_direction'] fast_pixel_direction_value = float(fast_pixel_direction[()]) fast_pixel_direction_units = fast_pixel_direction.attrs['units'] fast_pixel_direction_vector = fast_pixel_direction.attrs['vector'] fast_pixel_direction_value = convert_units(fast_pixel_direction_value, fast_pixel_direction_units, "mm") fast_axis = matrix.col(fast_pixel_direction_vector).normalize() # Get the slow pixel size and vector slow_pixel_direction = nx_module['slow_pixel_direction'] slow_pixel_direction_value = float(slow_pixel_direction[()]) slow_pixel_direction_units = slow_pixel_direction.attrs['units'] slow_pixel_direction_vector = slow_pixel_direction.attrs['vector'] slow_pixel_direction_value = convert_units(slow_pixel_direction_value, slow_pixel_direction_units, "mm") slow_axis = matrix.col(slow_pixel_direction_vector).normalize() # Get the origin vector module_offset = nx_module['module_offset'] origin = construct_vector(nx_file, module_offset.name) # Ensure that fast and slow axis are orthogonal normal = fast_axis.cross(slow_axis) slow_axis = -fast_axis.cross(normal) # Compute the attenuation coefficient. # This will fail for undefined composite materials # mu_at_angstrom returns cm^-1, but need mu in mm^-1 if material == 'Si': pass elif material == 'Silicon': material = 'Si' elif material == 'Sillicon': material = 'Si' elif material == 'CdTe': pass elif material == 'GaAs': pass else: raise RuntimeError('Unknown material: %s' % material) table = attenuation_coefficient.get_table(material) wavelength = beam.get_wavelength() mu = table.mu_at_angstrom(wavelength) / 10.0 # Construct the detector model pixel_size = (fast_pixel_direction_value, slow_pixel_direction_value) image_size = tuple(map(int, nx_module['data_size'])) self.model = Detector() self.model.add_panel( Panel(detector_type, detector_name, tuple(fast_axis), tuple(slow_axis), tuple(origin), pixel_size, image_size, trusted_range, thickness_value, material, mu)) # Set the parallax correction for panel in self.model: panel.set_px_mm_strategy( ParallaxCorrectedPxMmStrategy(mu, thickness_value)) panel.set_type('SENSOR_PAD')
def load_detector(entry): from dxtbx.model import Detector from scitbx import matrix # Get the detector module object nx_instrument = get_nx_instrument(entry, "instrument") nx_detector = get_nx_detector(nx_instrument, "detector") assert(nx_detector['depends_on'].value == '.') material = nx_detector['sensor_material'].value det_type = nx_detector['type'].value thickness = nx_detector['sensor_thickness'].value trusted_range = (nx_detector['underload'].value, nx_detector['saturation_value'].value) # The detector model detector = Detector() i = 0 while True: try: module = get_nx_detector_module(nx_detector, "module%d" % i) except Exception: break # Set the data size image_size = module['data_size'] # Set the module offset offset_length = module['module_offset'].value assert(module['module_offset'].attrs['depends_on'] == '.') assert(module['module_offset'].attrs['transformation_type'] == 'translation') assert(tuple(module['module_offset'].attrs['offset']) == (0, 0, 0)) offset_vector = matrix.col(module['module_offset'].attrs['vector']) origin = offset_vector * offset_length # Write the fast pixel direction module_offset_path = str(module['module_offset'].name) pixel_size_x = module['fast_pixel_direction'].value assert(module['fast_pixel_direction'].attrs['depends_on'] == module_offset_path) assert(module['fast_pixel_direction'].attrs['transformation_type'] == 'translation') assert(tuple(module['fast_pixel_direction'].attrs['offset']) == (0, 0, 0)) fast_axis = tuple(module['fast_pixel_direction'].attrs['vector']) # Write the slow pixel direction pixel_size_y = module['slow_pixel_direction'].value assert(module['slow_pixel_direction'].attrs['depends_on'] == module_offset_path) assert(module['slow_pixel_direction'].attrs['transformation_type'] == 'translation') assert(tuple(module['slow_pixel_direction'].attrs['offset']) == (0, 0, 0)) slow_axis = tuple(module['slow_pixel_direction'].attrs['vector']) # Get the pixel size and axis vectors pixel_size = (pixel_size_x, pixel_size_y) # Create the panel panel = detector.add_panel() panel.set_frame(fast_axis, slow_axis, origin) panel.set_pixel_size(pixel_size) panel.set_image_size(image_size) panel.set_type(det_type) panel.set_thickness(thickness) panel.set_material(material) panel.set_trusted_range(trusted_range) i += 1 # Return the detector and panel return detector
def _detector(self): """Return a model for a simple detector, presuming no one has one of these on a two-theta stage. Assert that the beam centre is provided in the Mosflm coordinate frame.""" if not self._multi_panel: detector = FormatCBFMini._detector(self) for f0, f1, s0, s1 in determine_pilatus_mask(detector): detector[0].add_mask(f0 - 1, s0 - 1, f1, s1) return detector # got to here means 60-panel version d = Detector() distance = float( self._cif_header_dictionary["Detector_distance"].split()[0]) beam_xy = (self._cif_header_dictionary["Beam_xy"].replace( "(", "").replace(")", "").replace(",", "").split()[:2]) beam_x, beam_y = map(float, beam_xy) wavelength = float( self._cif_header_dictionary["Wavelength"].split()[0]) pixel_xy = (self._cif_header_dictionary["Pixel_size"].replace( "m", "").replace("x", "").split()) pixel_x, pixel_y = map(float, pixel_xy) thickness = float( self._cif_header_dictionary["Silicon"].split()[2]) * 1000.0 nx = int( self._cif_header_dictionary["X-Binary-Size-Fastest-Dimension"]) ny = int(self._cif_header_dictionary["X-Binary-Size-Second-Dimension"]) overload = int(self._cif_header_dictionary["Count_cutoff"].split()[0]) underload = -1 # take into consideration here the thickness of the sensor also the # wavelength of the radiation (which we have in the same file...) table = attenuation_coefficient.get_table("Si") mu = table.mu_at_angstrom(wavelength) / 10.0 t0 = thickness # FIXME would also be very nice to be able to take into account the # misalignment of the individual modules given the calibration... # single detector or multi-module detector pixel_x *= 1000.0 pixel_y *= 1000.0 distance *= 1000.0 beam_centre = matrix.col((beam_x * pixel_x, beam_y * pixel_y, 0)) fast = matrix.col((1.0, 0.0, 0.0)) slow = matrix.col((0.0, -1.0, 0.0)) s0 = matrix.col((0, 0, -1)) origin = (distance * s0) - (fast * beam_centre[0]) - (slow * beam_centre[1]) root = d.hierarchy() root.set_local_frame(fast.elems, slow.elems, origin.elems) det = _DetectorDatabase["Pilatus"] # Edge dead areas not included, only gaps between modules matter n_fast, remainder = divmod(nx, det.module_size_fast) assert (n_fast - 1) * det.gap_fast == remainder n_slow, remainder = divmod(ny, det.module_size_slow) assert (n_slow - 1) * det.gap_slow == remainder mx = det.module_size_fast my = det.module_size_slow dx = det.gap_fast dy = det.gap_slow xmins = [(mx + dx) * i for i in range(n_fast)] xmaxes = [mx + (mx + dx) * i for i in range(n_fast)] ymins = [(my + dy) * i for i in range(n_slow)] ymaxes = [my + (my + dy) * i for i in range(n_slow)] self.coords = {} fast = matrix.col((1.0, 0.0, 0.0)) slow = matrix.col((0.0, 1.0, 0.0)) panel_idx = 0 for ymin, ymax in zip(ymins, ymaxes): for xmin, xmax in zip(xmins, xmaxes): xmin_mm = xmin * pixel_x ymin_mm = ymin * pixel_y origin_panel = fast * xmin_mm + slow * ymin_mm panel_name = "Panel%d" % panel_idx panel_idx += 1 p = d.add_panel() p.set_type("SENSOR_PAD") p.set_name(panel_name) p.set_raw_image_offset((xmin, ymin)) p.set_image_size((xmax - xmin, ymax - ymin)) p.set_trusted_range((underload, overload)) p.set_pixel_size((pixel_x, pixel_y)) p.set_thickness(thickness) p.set_material("Si") p.set_mu(mu) p.set_px_mm_strategy(ParallaxCorrectedPxMmStrategy(mu, t0)) p.set_local_frame(fast.elems, slow.elems, origin_panel.elems) p.set_raw_image_offset((xmin, ymin)) self.coords[panel_name] = (xmin, ymin, xmax, ymax) return d
def convert_crystfel_to_dxtbx(geom_filename, output_filename, detdist_override=None): """ :param geom_filename: a crystfel geometry file https://www.desy.de/~twhite/crystfel/manual-crystfel_geometry.html :param output_filename: filename for a dxtbx experiment containing a single detector model (this is a json file) :param detdist_override: alter the detector distance stored in the crystfel geometry to this value (in millimeters) """ geom = load_crystfel_geometry(geom_filename) dxtbx_det = Detector() for panel_name in geom['panels'].keys(): P = geom['panels'][panel_name] FAST = P['fsx'], P['fsy'], P['fsz'] SLOW = P['ssx'], P['ssy'], P['ssz'] # dxtbx uses millimeters pixsize = 1 / P['res'] # meters pixsize_mm = pixsize * 1000 detdist = P['coffset'] + P['clen'] # meters detdist_mm = detdist * 1000 if detdist_override is not None: detdist_mm = detdist_override # dxtbx and crystfel both identify the outer corner of the first pixel in memory as the origin of the panel origin = P['cnx'] * pixsize_mm, P[ 'cny'] * pixsize_mm, -detdist_mm # dxtbx assumes crystal as at point 0,0,0 num_fast_pix = P["max_fs"] - P['min_fs'] + 1 num_slow_pix = P["max_ss"] - P['min_ss'] + 1 panel_description = { 'fast_axis': FAST, 'gain': 1.0, # I dont think nanoBragg cares about this parameter 'identifier': '', 'image_size': (num_fast_pix, num_slow_pix), 'mask': [], 'material': 'Si', 'mu': 0, # NOTE for a thick detector set this to appropriate value 'name': panel_name, 'origin': origin, 'pedestal': 0.0, # I dont think nanoBragg cares about this parameter 'pixel_size': (pixsize_mm, pixsize_mm), 'px_mm_strategy': { 'type': 'SimplePxMmStrategy' }, 'raw_image_offset': (0, 0), # not sure what this is 'slow_axis': SLOW, 'thickness': 0, # note for a thick detector set this to appropriate value 'trusted_range': (-1.0, 1e6), # set as you wish 'type': 'SENSOR_PAD' } dxtbx_node = Panel.from_dict(panel_description) dxtbx_det.add_panel(dxtbx_node) E = Experiment() E.detector = dxtbx_det El = ExperimentList() El.append(E) El.as_file(output_filename) # this can be loaded into nanoBragg
def load_detector(entry): from dxtbx.model import Detector # Get the detector module object nx_instrument = get_nx_instrument(entry, "instrument") nx_detector = get_nx_detector(nx_instrument, "detector") assert nx_detector["depends_on"][()] == "." material = nx_detector["sensor_material"][()] det_type = nx_detector["type"][()] thickness = nx_detector["sensor_thickness"][()] trusted_range = (nx_detector["underload"][()], nx_detector["saturation_value"][()]) # The detector model detector = Detector() i = 0 while True: try: module = get_nx_detector_module(nx_detector, "module%d" % i) except Exception: break # Set the data size image_size = module["data_size"] # Set the module offset offset_length = module["module_offset"][()] assert module["module_offset"].attrs["depends_on"] == "." assert module["module_offset"].attrs[ "transformation_type"] == "translation" assert tuple(module["module_offset"].attrs["offset"]) == (0, 0, 0) offset_vector = matrix.col(module["module_offset"].attrs["vector"]) origin = offset_vector * offset_length # Write the fast pixel direction module_offset_path = str(module["module_offset"].name) pixel_size_x = module["fast_pixel_direction"][()] assert module["fast_pixel_direction"].attrs[ "depends_on"] == module_offset_path assert (module["fast_pixel_direction"].attrs["transformation_type"] == "translation") assert tuple(module["fast_pixel_direction"].attrs["offset"]) == (0, 0, 0) fast_axis = tuple(module["fast_pixel_direction"].attrs["vector"]) # Write the slow pixel direction pixel_size_y = module["slow_pixel_direction"][()] assert module["slow_pixel_direction"].attrs[ "depends_on"] == module_offset_path assert (module["slow_pixel_direction"].attrs["transformation_type"] == "translation") assert tuple(module["slow_pixel_direction"].attrs["offset"]) == (0, 0, 0) slow_axis = tuple(module["slow_pixel_direction"].attrs["vector"]) # Get the pixel size and axis vectors pixel_size = (pixel_size_x, pixel_size_y) # Create the panel panel = detector.add_panel() panel.set_frame(fast_axis, slow_axis, origin) panel.set_pixel_size(pixel_size) panel.set_image_size([int(x) for x in image_size]) panel.set_type(det_type) panel.set_thickness(thickness) panel.set_material(material) panel.set_trusted_range([float(x) for x in trusted_range]) i += 1 # Return the detector and panel return detector
class FormatNexusJungfrauHack(FormatNexus): @staticmethod def understand(image_file): try: with h5py.File(image_file, "r") as handle: return "/entry/instrument/JF1M" in handle except OSError: return False def _start(self): # Read the file structure self._reader = reader = NXmxReader(self._image_file) # Only support 1 set of models at the moment assert len(reader.entries) == 1, "Currently only supports 1 NXmx entry" assert len(reader.entries[0].data) == 1, "Currently only supports 1 NXdata" assert ( len(reader.entries[0].instruments) == 1 ), "Currently only supports 1 NXinstrument" assert len(reader.entries[0].samples) == 1, "Currently only supports 1 NXsample" assert ( len(reader.entries[0].samples[0].beams) == 1 or len(reader.entries[0].instruments[0].beams) == 1 ), "Currently only supports 1 NXbeam" # Get the NXmx model objects entry = reader.entries[0] self.instrument = instrument = entry.instruments[0] detector = instrument.detectors[0] sample = entry.samples[0] beam = sample.beams[0] if sample.beams else instrument.beams[0] data = entry.data[0] # Construct the models self._beam_factory = BeamFactory(beam) self._beam_factory.load_model(0) self._setup_detector(detector, self._beam_factory.model) self._setup_gonio_and_scan(sample, detector) if self._scan_model: array_range = self._scan_model.get_array_range() num_images = array_range[1] - array_range[0] else: num_images = 0 self._raw_data = DataFactory(data, max_size=num_images) def _setup_detector(self, detector, beam): nx_detector = detector.handle nx_module = detector.modules[0].handle # Get the detector name and type if "type" in nx_detector: detector_type = str(nx_detector["type"][()]) else: detector_type = "unknown" detector_name = str(nx_detector.name) # Get the trusted range of pixel values if "saturation_value" in nx_detector: trusted_range = (-1, float(nx_detector["saturation_value"][()])) else: trusted_range = (-1, 99999999) # Get the detector thickness thickness = nx_detector["sensor_thickness"] thickness_value = float(thickness[()]) thickness_units = thickness.attrs["units"] thickness_value = float(convert_units(thickness_value, thickness_units, "mm")) # Get the detector material material = nx_detector["sensor_material"][()] if hasattr(material, "shape"): material = "".join(m.decode() for m in material) detector_material = clean_string(str(material)) material = { "Si": "Si", np.string_("Si"): "Si", np.string_("Silicon"): "Si", np.string_("Sillicon"): "Si", np.string_("CdTe"): "CdTe", np.string_("GaAs"): "GaAs", }.get(detector_material) if not material: raise RuntimeError("Unknown material: %s" % detector_material) try: x_pixel = nx_detector["x_pixel_size"][()] * 1000.0 y_pixel = nx_detector["y_pixel_size"][()] * 1000.0 legacy_beam_x = float(x_pixel * nx_detector["beam_center_x"][()]) legacy_beam_y = float(y_pixel * nx_detector["beam_center_y"][()]) legacy_distance = float(1000 * nx_detector["detector_distance"][()]) except KeyError: legacy_beam_x = 0 legacy_beam_y = 0 # Get the fast pixel size and vector fast_pixel_direction = nx_module["fast_pixel_direction"] fast_pixel_direction_value = float(fast_pixel_direction[()]) fast_pixel_direction_units = fast_pixel_direction.attrs["units"] fast_pixel_direction_vector = fast_pixel_direction.attrs["vector"] fast_pixel_direction_value = convert_units( fast_pixel_direction_value, fast_pixel_direction_units, "mm" ) fast_axis = matrix.col(fast_pixel_direction_vector).normalize() # Get the slow pixel size and vector slow_pixel_direction = nx_module["slow_pixel_direction"] slow_pixel_direction_value = float(slow_pixel_direction[()]) slow_pixel_direction_units = slow_pixel_direction.attrs["units"] slow_pixel_direction_vector = slow_pixel_direction.attrs["vector"] slow_pixel_direction_value = convert_units( slow_pixel_direction_value, slow_pixel_direction_units, "mm" ) slow_axis = matrix.col(slow_pixel_direction_vector).normalize() origin = matrix.col((0.0, 0.0, -legacy_distance)) if origin.elems[0] == 0.0 and origin.elems[1] == 0: origin = -(origin + legacy_beam_x * fast_axis + legacy_beam_y * slow_axis) # Change of basis to convert from NeXus to IUCr/ImageCIF convention cob = matrix.sqr((-1, 0, 0, 0, 1, 0, 0, 0, -1)) origin = cob * matrix.col(origin) fast_axis = cob * fast_axis slow_axis = cob * slow_axis # Ensure that fast and slow axis are orthogonal normal = fast_axis.cross(slow_axis) slow_axis = -fast_axis.cross(normal) # Compute the attenuation coefficient. # This will fail for undefined composite materials # mu_at_angstrom returns cm^-1, but need mu in mm^-1 table = attenuation_coefficient.get_table(material) wavelength = beam.get_wavelength() mu = table.mu_at_angstrom(wavelength) / 10.0 # Construct the detector model pixel_size = (fast_pixel_direction_value, slow_pixel_direction_value) # image size stored slow to fast but dxtbx needs fast to slow image_size = tuple(int(x) for x in reversed(nx_module["data_size"][-2:])) image_size = (1030, 1064) self.model = Detector() self.model.add_panel( Panel( detector_type, detector_name, tuple(fast_axis), tuple(slow_axis), tuple(origin), pixel_size, image_size, trusted_range, thickness_value, material, mu, ) ) # Set the parallax correction for panel in self.model: panel.set_px_mm_strategy(ParallaxCorrectedPxMmStrategy(mu, thickness_value)) panel.set_type("SENSOR_PAD") self._detector_model = self.model def _setup_gonio_and_scan(self, sample, detector): with h5py.File(self._image_file, "r") as handle: phi = handle["/entry/sample/goniometer/omega"][()] image_range = (1, len(phi)) oscillation = (float(phi[0]), float(phi[1] - phi[0])) # Get the exposure time num_images = len(phi) exposure_time = flex.double(num_images, 0) epochs = flex.double(num_images, 0.0) for i in range(1, len(epochs)): epochs[i] = epochs[i - 1] + exposure_time[i - 1] self._scan_model = Scan(image_range, oscillation, exposure_time, epochs) self._goniometer_model = self._goniometer_factory.known_axis((-1, 0, 0)) def _end(self): return def _goniometer(self): return self._goniometer_model def _detector(self): return self._detector_model def _scan(self): return self._scan_model def get_goniometer(self, index=None): return self._goniometer() def get_detector(self, index=None): return self._detector() def get_beam(self, index=None): return self._beam(index) def get_scan(self, index=None): if index is None: return self._scan() scan = self._scan() if scan is not None: return scan[index] return scan def get_raw_data(self, index): return self._raw_data[index] def get_static_mask(self, index=None, goniometer=None): return MaskFactory(self.instrument.detectors, index).mask def get_num_images(self): if self._scan() is not None: return self._scan().get_num_images() return len(self._raw_data) def get_image_file(self, index=None): return self._image_file def get_detectorbase(self, index=None): raise NotImplementedError @staticmethod def get_instrument_name(handle): if "short_name" in handle["/entry/instrument"].attrs: name = handle["/entry/instrument"].attrs["short_name"] elif "/entry/instrument/name" in handle: if "short_name" in handle["/entry/instrument/name"].attrs: name = handle["/entry/instrument/name"].attrs["short_name"] else: name = handle["/entry/instrument/name"][()] else: name = None return name