def test_write_read_crystfel_file(tmpdir): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) path = str(tmpdir / 'test.geom') geom.write_crystfel_geom(filename=path, photon_energy=9000, adu_per_ev=0.0075, clen=0.2) loaded = AGIPD_1MGeometry.from_crystfel_geom(path) np.testing.assert_allclose(loaded.modules[0][0].corner_pos, geom.modules[0][0].corner_pos) np.testing.assert_allclose(loaded.modules[0][0].fs_vec, geom.modules[0][0].fs_vec) # Load the geometry file with cfelpyutils and test the rigid groups geom_dict = load_crystfel_geometry(path) quad_gr0 = [ # 1st quadrant: p0a0 ... p3a7 'p{}a{}'.format(p, a) for p, a in product(range(4), range(8)) ] assert geom_dict['rigid_groups']['p0'] == quad_gr0[:8] assert geom_dict['rigid_groups']['p3'] == quad_gr0[-8:] assert geom_dict['rigid_groups']['q0'] == quad_gr0 assert geom_dict['panels']['p0a0']['res'] == 5000 # 5000 pixels/metre p3a7 = geom_dict['panels']['p3a7'] assert p3a7['min_ss'] == 448 assert p3a7['max_ss'] == 511 assert p3a7['min_fs'] == 0 assert p3a7['max_fs'] == 127
def test_compare(): geom1 = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) geom2 = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-527, 625), (-548, -10), (520, -162), (542.5, 473)]) # Smoketest ax = geom1.compare(geom2) assert isinstance(ax, Axes)
def from_crystfel_geom(cls, filename): """Load geometry from crystfel geometry.""" try: exgeom_obj = AGIPD_1MGeometry.from_crystfel_geom(filename) except KeyError: # Probably some informations like clen and adu_per_eV missing with open(filename) as f: geom_file = f.read() with tempfile.NamedTemporaryFile() as temp: with open(temp.name, 'w') as f: f.write("""clen = 0.118 adu_per_eV = 0.0075 """ + geom_file) exgeom_obj = AGIPD_1MGeometry.from_crystfel_geom(temp.name) return cls(exgeom_obj)
def test_snap_assemble_data(): def check_result(img, centre): assert img.shape == (1256, 1092) assert tuple(centre) == (631, 550) assert np.isnan(img[0, 0]) assert img[50, 50] == 0 geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) stacked_data = np.zeros((16, 512, 128)) img, centre = geom.position_modules_fast(stacked_data) check_result(img, centre) # test unsafe cast with output array stacked_data = np.zeros((16, 512, 128), dtype=np.float64) out = geom.output_array_for_position_fast(dtype=np.float32) with pytest.raises(TypeError): img, centre = geom.position_modules_fast(stacked_data, out=out) # test safe cast with output array stacked_data = np.zeros((16, 512, 128), dtype=np.uint16) out = geom.output_array_for_position_fast(dtype=np.float32) img, centre = geom.position_modules_fast(stacked_data, out=out) check_result(img, centre) check_result(out, centre) assert img.dtype == out.dtype == np.float32 # Assemble in parallel stacked_data = np.zeros((16, 512, 128)) with ThreadPoolExecutor(max_workers=2) as tpool: img, centre = geom.position_modules_fast(stacked_data, threadpool=tpool) check_result(img, centre)
def _load_geometry(self, filename, quad_positions): """Override.""" if self._assembler_type == GeomAssembler.OWN or self._stack_only: raise AssemblingError("Not implemented!") else: from extra_geom import AGIPD_1MGeometry try: self._geom = AGIPD_1MGeometry.from_crystfel_geom(filename) except (ImportError, ModuleNotFoundError, OSError) as e: raise AssemblingError(e)
def test_get_pixel_positions(): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) pixelpos = geom.get_pixel_positions() assert pixelpos.shape == (16, 512, 128, 3) px = pixelpos[..., 0] py = pixelpos[..., 1] assert -0.12 < px.min() < -0.1 assert 0.12 > px.max() > 0.1 assert -0.14 < py.min() < -0.12 assert 0.14 > py.max() > 0.12
def test_to_distortion_array(): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) # Smoketest distortion = geom.to_distortion_array() assert isinstance(distortion, np.ndarray) assert distortion.shape == (8192, 128, 4, 3) # Coordinates in m, origin at corner; max x & y should be ~ 25cm assert 0.20 < distortion[..., 1].max() < 0.30 assert 0.20 < distortion[..., 2].max() < 0.30 assert 0.0 <= distortion[..., 1].min() < 0.01 assert 0.0 <= distortion[..., 2].min() < 0.01
def test_write_read_crystfel_file_2d(tmpdir): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) path = str(tmpdir / 'test.geom') geom.write_crystfel_geom(filename=path, dims=('frame', 'ss', 'fs'), adu_per_ev=0.0075, clen=0.2) loaded = AGIPD_1MGeometry.from_crystfel_geom(path) np.testing.assert_allclose(loaded.modules[0][0].corner_pos, geom.modules[0][0].corner_pos) np.testing.assert_allclose(loaded.modules[0][0].fs_vec, geom.modules[0][0].fs_vec) # Load the geometry file with cfelpyutils and check some values geom_dict = load_crystfel_geometry(path) p3a7 = geom_dict['panels']['p3a7'] assert p3a7['dim_structure'] == ['%', 'ss', 'fs'] assert p3a7['min_ss'] == (3 * 512) + 448 assert p3a7['max_ss'] == (3 * 512) + 511 assert p3a7['min_fs'] == 0 assert p3a7['max_fs'] == 127
def test_assemble_symmetric(): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) print("2 centre", geom._snapped().centre * 2) stacked_data = np.zeros((16, 512, 128)) img = geom.position_modules_symmetric(stacked_data) assert img.shape == (1262, 1100) assert np.isnan(img[0, 0]) assert np.isnan(img[img.shape[0] // 2, img.shape[1] // 2]) assert img[50, 50] == 0 # Smoketest assembling into suitable output array geom.position_modules_symmetric(stacked_data, out=img) with pytest.raises(ValueError): # Output array not big enough geom.position_modules_symmetric(stacked_data, out=img[:-1, :-1])
def test_data_coords_to_positions(): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) module_no = np.zeros(16, dtype=np.int16) slow_scan = np.linspace(0, 500, num=16, dtype=np.float32) fast_scan = np.zeros(16, dtype=np.float32) res = geom.data_coords_to_positions(module_no, slow_scan, fast_scan) assert res.shape == (16, 3) resx, resy, resz = res.T np.testing.assert_allclose(resz, 0) np.testing.assert_allclose(resy, 625 * geom.pixel_size) assert (np.diff(resx) > 0).all() # Monotonically increasing np.testing.assert_allclose(resx[0], -525 * geom.pixel_size) assert -0.01 < resx[-1] < 0.01
def test_inspect(): geom = AGIPD_1MGeometry.from_quad_positions( quad_pos=[(-525, 625), (-550, -10), (520, -160), (542.5, 475)]) # Smoketest ax = geom.inspect() assert isinstance(ax, Axes)
def test_quad_positions(): quad_pos = [(-525, 625), (-550, -10), (520, -160), (542.5, 475)] geom = AGIPD_1MGeometry.from_quad_positions(quad_pos) np.testing.assert_allclose(geom.quad_positions(), quad_pos)
import geoAssembler.optimiser as centreOptimiser import geoAssembler from extra_geom import AGIPD_1MGeometry import numpy as np import os.path geom = AGIPD_1MGeometry.from_quad_positions(quad_pos=[ (-525, 625), (-550, -10), (520, -160), (542.5, 475), ]) stacked_mean_path = os.path.dirname( geoAssembler.__file__) + "/tests/optimiser-test-frame.npy" def test_integrator(): stacked_mean = np.load(stacked_mean_path) optimiser = centreOptimiser.CentreOptimiser(geom, stacked_mean, sample_dist_m=0.2) integrated_result = optimiser.integrate2d(optimiser.frame) misaligned_2dint = integrated_result.intensity misaligned_2dint_r = integrated_result.radial misaligned_2dint_a = integrated_result.azimuthal
def from_quad_positions(cls, quad_pos=None): """Generate geometry from quadrant positions.""" quad_pos = quad_pos or Defaults.fallback_quad_pos[cls.detector_name] exgeom_obj = AGIPD_1MGeometry.from_quad_positions(quad_pos) return cls(exgeom_obj)
@classmethod def from_h5_file_and_quad_positions(cls, geom_file, quad_pos=None): """Create geometry from geometry file or quad positions.""" quad_pos = quad_pos or Defaults.fallback_quad_pos[cls.detector_name] exgeom_obj = LPD_1MGeometry.from_h5_file_and_quad_positions( geom_file, quad_pos) return cls(exgeom_obj, geom_file) @property def quad_pos(self): """Get the quadrant positions from the geometry object.""" quad_pos = self.exgeom_obj.quad_positions(self.filename) return pd.DataFrame(quad_pos, columns=['X', 'Y'], index=['q{}'.format(i) for i in range(1, 5)]) GEOM_MODULES = {'AGIPD': AGIPDGeometry, 'LPD': LPDGeometry} if __name__ == '__main__': geom = AGIPD_1MGeometry.from_quad_positions(quad_pos=[ (-525, 625), (-550, -10), (520, -160), (542.5, 475), ]) geom.write_crystfel_geom('sample.geom') geom = AGIPD_1MGeometry.from_crystfel_geom('sample.geom')
def __init__(self, args): # constructor of the parent class first Agipd2nexus.__init__(self, args) # self.hierarchy = read_geom(self.params.geom_file) # TODO: optionally from the conf file self.hierarchy = Adipd.from_crystfel_geom(args.geom) # if self.params.cxi_file and Path(self.params.cxi_file).exists(): # cxi = h5py.File(self.params.cxi_file, 'r') # else: # cxi = None if self.params['detector_distance'] is None: # try to take from the geometry file: if self.hierarchy.detector_distance: self.params.detector_distance = self.hierarchy.detector_distance else: raise Exception( "Detector distance is undefined! You should set it either in `.phil` or in `.geom` files, " "or pass as a command line argument: `detector_distance=123.45` (in mm)" ) if self.params['wavelength'] is None: # try to take from the geometry file: if self.hierarchy.incident_wavelength: self.params.wavelength = self.hierarchy.incident_wavelength else: raise Exception( "Incident wavelength is undefined! You should set it either in `.phil` or in `.geom` files, " "or pass as a command line argument: `wavelength=1.2345` (in angstrom)" ) self.n_quads = 4 self.n_modules = 4 self.n_asics = 8 # ==== CREATE DETECTOR MODULES ==== """ Add 4 quadrants Nexus coordiate system, into the board AGIPD detector o --------> (+x) Q3=(12,13,14,15) Q0=(0,1,2,3) | o | Q2=(8,9,10,11) Q1=(4,5,6,7) v (+y) """ panels = [] for q, quad in self.hierarchy.items(): for m, module in quad.items(): panels.extend([module[key] for key in module]) fast = max([int(panel['max_fs']) for panel in panels]) + 1 slow = max([int(panel['max_ss']) for panel in panels]) + 1 pixel_size = panels[0]['pixel_size'] assert [ pixel_size == panels[i + 1]['pixel_size'] for i in range(len(panels) - 1) ].count(False) == 0 quad_fast = fast quad_slow = slow * self.n_quads module_fast = quad_fast module_slow = quad_slow // self.n_quads asic_fast = module_fast asic_slow = module_slow // self.n_asics self.group_rules = { 'NXdetector': { 'names': ['ELE_D0'] }, 'NXdetector_group': { 'names': ['AGIPD'] }, 'NXtransformations': {}, 'NXdetector_module': { 'names': [] } # 'names' will be populated below } array_name = 'ARRAY_D0' det_path = '/entry/instrument/ELE_D0/' t_path = det_path + 'transformations/' class Transform(NexusElement): def __init__(self, name: str, value: Any = [0.0], attrs: dict = None) -> None: default_attrs = { 'equipment': 'detector', 'transformation_type': 'rotation', 'units': 'degrees', 'offset_units': 'mm', 'vector': (0., 0., -1.) } NexusElement.__init__(self, full_path=t_path + name, value=value, nxtype=NxType.field, dtype='f', attrs={ **default_attrs, **attrs }) det_field_rules = {} # extends mandatory field rules det_additional_rules = { } # additional transformation elements (fields) for the detector for quad in range(self.n_quads): # iterate quadrants q_key = f"q{quad}" q_name = f"AXIS_D0Q{quad}" quad_vector = self.hierarchy[q_key].local_origin.elems q_elem = Transform(q_name, attrs={ 'depends_on': t_path + 'AXIS_D0', 'offset': quad_vector, 'equipment_component': 'detector_quad' }) det_additional_rules[t_path + q_name] = q_elem for module_num in range( self.n_modules): # iterate modules within a quadrant m_key = f"p{(quad * self.n_modules) + module_num}" m_name = f"AXIS_D0Q{quad}M{module_num}" module_vector = self.hierarchy[q_key][m_key].local_origin.elems m_elem = Transform(m_name, attrs={ 'depends_on': t_path + q_name, 'equipment_component': 'detector_module', 'offset': module_vector }) det_additional_rules[t_path + m_name] = m_elem for asic_num in range( self.n_asics): # iterate asics within a module a_key = f"p{(quad * self.n_modules) + module_num}a{asic_num}" a_name = f"AXIS_D0Q{quad}M{module_num}A{asic_num}" asic_vector = self.hierarchy[q_key][m_key][a_key][ 'local_origin'].elems a_elem = Transform(a_name, attrs={ 'depends_on': t_path + m_name, 'equipment_component': 'detector_asic', 'offset': asic_vector }) det_additional_rules[t_path + a_name] = a_elem det_module_name = array_name + f"Q{quad}M{module_num}A{asic_num}" # populate ``group_rules`` with detector modules: self.group_rules['NXdetector_module']['names'] += [ det_module_name ] def full_m_name(name: str) -> str: return det_path + det_module_name + '/' + name det_field_rules[full_m_name('data_origin')] = np.array( [(quad * self.n_modules) + module_num, asic_slow * asic_num, 0], dtype='i') det_field_rules[full_m_name('data_size')] = np.array( [1, asic_slow, asic_fast], dtype='i') fast = self.hierarchy[q_key][m_key][a_key][ 'local_fast'].elems slow = self.hierarchy[q_key][m_key][a_key][ 'local_slow'].elems det_field_rules[full_m_name( 'fast_pixel_direction')] = NexusElement( full_path=full_m_name('fast_pixel_direction'), value=[pixel_size], dtype='f', nxtype=NxType.field, attrs={ 'depends_on': t_path + f'AXIS_D0Q{quad}M{module_num}A{asic_num}', 'transformation_type': 'translation', 'units': 'mm', 'vector': fast, 'offset': (0., 0., 0.) }) det_field_rules[full_m_name( 'slow_pixel_direction')] = NexusElement( full_path=full_m_name('slow_pixel_direction'), value=[pixel_size], dtype='f', nxtype=NxType.field, attrs={ 'depends_on': t_path + f'AXIS_D0Q{quad}M{module_num}A{asic_num}', 'transformation_type': 'translation', 'units': 'mm', 'vector': slow, 'offset': (0., 0., 0.) }) self.field_rules = { # '/entry/definition': np.string_(f"NXmx:{get_git_revision_hash()}"), # TODO: _create_scalar? '/entry/definition': np.string_( "NXmx" ), # XXX: whoa! this is THE criteria of being a "nexus format" ! '/entry/file_name': np.string_(self.output_file_name), # '/entry/start_time': np.string_(self.params.nexus_details.start_time), '/entry/start_time': np.string_( '2000-10-10T00:00:00.000Z'), # FIXME: what is the real data? # '/entry/end_time': np.string_(self.params.nexus_details.end_time), '/entry/end_time': np.string_('2000-10-10T01:00:00.000Z'), # '/entry/end_time_estimated': np.string_(self.params.nexus_details.end_time_estimated), '/entry/end_time_estimated': np.string_('2000-10-10T01:00:00.000Z'), '/entry/data/data': LazyFunc(cxi.copy, "entry_1/data_1/data", "entry/data"), '/entry/instrument/name': NexusElement(full_path='/entry/instrument/name', value=self.params.nexus_details.instrument_name, nxtype=NxType.field, dtype=h5py_str(), attrs={ 'short_name': self.params.nexus_details.instrument_short_name }), '/entry/instrument/AGIPD/group_index': np.array(list(range(1, 3)), dtype='i'), # XXX: why 16, not 2? '/entry/instrument/AGIPD/group_names': np.array([np.string_('AGIPD'), np.string_('ELE_D0')], dtype='S12'), '/entry/instrument/AGIPD/group_parent': np.array([-1, 1], dtype='i'), '/entry/instrument/beam/incident_wavelength': NexusElement( full_path='/entry/instrument/beam/incident_wavelength', value=self.params.wavelength, nxtype=NxType.field, dtype='f', attrs={'units': 'angstrom'}), '/entry/instrument/beam/total_flux': NexusElement(full_path='/entry/instrument/beam/total_flux', value=self.get_xgm_data(cxi), nxtype=NxType.field, dtype='f', attrs={'units': 'Hz'}), '/entry/instrument/ELE_D0/data': h5py.SoftLink('/entry/data/data'), '/entry/instrument/ELE_D0/sensor_material': "Si", # FIXME: move to the `phil`-file '/entry/instrument/ELE_D0/sensor_thickness': NexusElement( full_path='/entry/instrument/ELE_D0/sensor_thickness', value=300.0, # FIXME: move to the `phil`-file nxtype=NxType.field, dtype='f', attrs={'units': 'microns'}), '/entry/sample/depends_on': np.str('.'), # XXX: Why not `np.string_`?? '/entry/sample/name': NexusElement(full_path='/entry/sample/name', value=self.params.sample_name, nxtype=NxType.field, dtype=h5py_str()), '/entry/source/name': NexusElement(full_path='/entry/source/name', value=self.params.nexus_details.source_name, nxtype=NxType.field, dtype=h5py_str(), attrs={ 'short_name': self.params.nexus_details.source_short_name }), } self.field_rules = {**self.field_rules, **det_field_rules} self.additional_elements = { '/entry/instrument/AGIPD/group_type': NexusElement(full_path='/entry/instrument/AGIPD/group_type', value=[1, 2], nxtype=NxType.field, dtype='i'), f'{t_path}/AXIS_D0': Transform('AXIS_D0', attrs={ 'depends_on': t_path + 'AXIS_RAIL', 'equipment_component': 'detector_arm', 'offset': self.hierarchy.local_origin }), f'{t_path}/AXIS_RAIL': NexusElement(full_path=t_path + 'AXIS_RAIL', dtype='f', nxtype=NxType.field, value=[self.params.detector_distance], attrs={ 'depends_on': np.string_('.'), 'equipment': 'detector', 'equipment_component': 'detector_arm', 'transformation_type': 'translation', 'units': 'mm', 'vector': (0., 0., 1.), }), } self.additional_elements = { **self.additional_elements, **det_additional_rules } self.global_attrs = { 'NX_class': 'NXroot', 'file_name': self.output_file_name, 'file_time': str(dt.now()), 'HDF5_Version': h5py.version.hdf5_version }