def __init__(self, wavelength, focus_diameter, pulse_energy, profile_model=None, pulse_energy_variation=None, pulse_energy_spread=None, pulse_energy_variation_n=None, polarization="ignore"): self.photon = Photon(wavelength=wavelength) self.pulse_energy_mean = pulse_energy self.set_pulse_energy_variation(pulse_energy_variation, pulse_energy_spread, pulse_energy_variation_n) self.profile = Profile(model=profile_model, focus_diameter=focus_diameter) if polarization not in [ "vertical", "horizontal", "unpolarized", "ignore" ]: log_and_raise_error( logger, "polarization = \"%s\" is an invalid input for initialization of Source instance." ) return self.polarization = polarization log_debug(logger, "Source configured")
def set_custom_geometry_by_mrcfile(self, filename, offset=None, factor=None): """ Read map from the MRC file (CCP4 file format, see http://www.ccp4.ac.uk/html/maplib.html). The map will be preprocessed by applying an offset and rescaling and by padding the water background with zeros. Finally, the avereage value of the map will be rescaled by the refractive index of the associated material. Args: :filename (str): Filename of MRC file. :offset (float): Offset value of the map (MAP = (EM_DATA + OFFSET) X FACTOR) :factor (float): Rescale factor of the map (MAP = (EM_DATA + OFFSET) X FACTOR) """ map3d, dx = condor.utils.emdio.read_map(filename) if offset is None and factor is None: ed_water = condor.utils.material.AtomDensityMaterial(material_type="water").get_electron_density() if len(self.materials) > 1: log_and_raise_error( logger, "More than one material defined. This is incompatible with automatic scaling of an EMD map." ) sys.exit(1) ed_particle = self.materials[0].get_electron_density() map3d = condor.utils.emdio.preproc_map_auto(map3d, ed_water=ed_water, ed_particle=ed_particle) else: map3d = condor.utils.emdio.perproc_map_manual(map3d, offset=offset, factor=factor) self.set_custom_geometry_by_array(map3d, dx)
def get_linear_sampling_ratio(self, wavelength = None, particle_diameter = None, particle_key = None): """ Returns the linear sampling ratio :math:`o` of the diffraction pattern: | :math:`o=\\frac{D\\lambda}{dp}` | :math:`D`: Detector distance | :math:`p`: Detector pixel size (edge length) | :math:`\\lambda`: Photon wavelength | :math:`d`: Particle diameter """ if wavelength is None: wavelength = self.source.photon.get_wavelength() detector_distance = self.detector.distance if particle_diameter is None: if len(self.particles) == 1: p = self.particles.values()[0] elif particle_key is None: log_and_raise_error(logger, "You need to specify a particle_key because there are more than one particle models.") else: p = self.particles[particle_key] particle_diameter = p.diameter_mean pN = utils.diffraction.nyquist_pixel_size(wavelength, detector_distance, particle_diameter) pD = self.detector.pixel_size ratio = pN/pD return ratio
def add_material(self, material_type, massdensity, atomic_composition, electron_density): """ Initialise and add the AtomDensityMaterial / ElectronDensityMaterial class instance to the particle Args: :material_type (str): See :class:`condor.utils.material.AtomDensityMaterial` :massdensity (float): See :class:`condor.utils.material.AtomDensityMaterial` :atomic_composition (dict): See :class:`condor.utils.material.AtomDensityMaterial` :electron_density (float): See :class:`condor.utils.material.ElectronDensityMaterial` """ if electron_density is None: self.materials.append( AtomDensityMaterial(material_type=material_type, massdensity=massdensity, atomic_composition=atomic_composition)) else: if massdensity is not None or atomic_composition is not None: log_and_raise_error( logger, r"An electron density is defined so material_type, massdensity and atomic_composition have to be all 'None'." ) return if material_type != "custom": log_and_raise_error( logger, r"An electron density is defined, so material_type must be \'custom\' but is %s." % material_type) return self.materials.append( ElectronDensityMaterial(electron_density=electron_density))
def __init__(self, shape): if len(shape) != 3: log_and_raise_error( logger, "%s is an invald shape for initialisation of MaterialMap.", str(shape)) self._shape = tuple(shape)
def get_p_max_dist(self, cx = None, cy = None, pos = "corner", center_variation = False): r""" Return 3D position vector of the pixel furthest away from the beam center. If each of the given center position coordinates (``cx``, ``cy``) is ``None`` the beam center is assumed to be located at its mean position Kwargs: :cx (float): *x*-coordinate of the center position in unit pixel (default ``None``) :cy (float): *y*-coordinate of the center position in unit pixel (default ``None``) :pos (str): Position constraint can be either ``pos='corner'`` or ``pos='edge'``. (default ``'corner'``) :center_variation (bool): If ``True`` the beam center variation is taken into account. With respect to the mean position a maximum deviation of *factor/2* times the variational spread is assumed. The *factor* is 3 for Gaussian distributed centers and 1 for others (default ``False``) """ x, y = self._get_xy_max_dist(cx=cx, cy=cy, center_variation=center_variation) xm = x*self.pixel_size ym = y*self.pixel_size log_debug(logger, "x = %.1f pix, y = %.1f pix" % (x, y)) p = numpy.array([0.,0.,self.distance]) if pos == "corner": p[0] = xm p[1] = ym elif pos == "edge": if abs(x) > abs(y): p[0] = xm else: p[1] = ym else: log_and_raise_error(logger, r"Invalid input: pos=%s. Input must be either 'corner' or 'edge'." % pos) return p
def experiment_from_configdict(configdict): """ Initialise Experiment instance from a dictionary *See also:* - :class:`condor.experiment.Experiment` - `Composing a configuration file <configdict.html>`_ """ # Source source = condor.Source(**configdict["source"]) # Particles particle_keys = [k for k in configdict.keys() if k.startswith("particle")] particles = {} if len(particle_keys) == 0: log_and_raise_error(logger, "No particles defined.") for k in particle_keys: if k.startswith("particle_sphere"): particles[k] = condor.ParticleSphere(**configdict[k]) elif k.startswith("particle_spheroid"): particles[k] = condor.ParticleSpheroid(**configdict[k]) elif k.startswith("particle_map"): particles[k] = condor.ParticleMap(**configdict[k]) elif k.startswith("particle_atoms"): particles[k] = condor.ParticleAtoms(**configdict[k]) else: log_and_raise_error(logger,"Particle model for %s is not implemented." % k) # Detector detector = condor.Detector(**configdict["detector"]) experiment = Experiment(source, particles, detector) return experiment
def set_custom_geometry_by_emd_id(self, emd_id, offset=None, factor=None): """ Fetch map from the EMD by id code. The map will be preprocessed by applying an offset and rescaling and by padding the water background with zeros. Finally, the avereage value of the map will be rescaled by the refractive index of the associated material. Args: :emd_id (str): EMD ID code. :offset (float): Offset value of the map (MAP = (EM_DATA + OFFSET) X FACTOR) :factor (float): Rescale factor of the map (MAP = (EM_DATA + OFFSET) X FACTOR) """ map3d, dx = condor.utils.emdio.fetch_map(emd_id) if offset is None and factor is None: ed_water = condor.utils.material.AtomDensityMaterial( material_type="water").get_electron_density() if len(self.materials) > 1: log_and_raise_error( logger, "More than one material defined. This is incompatible with automatic scaling of an EMD map." ) sys.exit(1) ed_particle = self.materials[0].get_electron_density() map3d = condor.utils.emdio.preproc_map_auto( map3d, ed_water=ed_water, ed_particle=ed_particle) else: map3d = condor.utils.emdio.perproc_map_manual(map3d, offset=offset, factor=factor) self.set_custom_geometry_by_array(map3d, dx)
def set_custom_geometry_by_emd_id(self, emd_id, offset=None, factor=None): """ Fetch map from the EMD by id code. The map will be preprocessed by applying an offset and rescaling and by padding the water background with zeros. Finally, the avereage value of the map will be rescaled by the refractive index of the associated material. Args: :emd_id (str): EMD ID code. :offset (float): Offset value of the map (MAP = (EM_DATA + OFFSET) X FACTOR) :factor (float): Rescale factor of the map (MAP = (EM_DATA + OFFSET) X FACTOR) """ map3d, dx = condor.utils.emdio.fetch_map(emd_id) if offset is None and factor is None: ed_water = condor.utils.material.AtomDensityMaterial(material_type="water").get_electron_density() if len(self.materials) > 1: log_and_raise_error( logger, "More than one material defined. This is incompatible with automatic scaling of an EMD map." ) sys.exit(1) ed_particle = self.materials[0].get_electron_density() map3d = condor.utils.emdio.preproc_map_auto(map3d, ed_water=ed_water, ed_particle=ed_particle) else: map3d = condor.utils.emdio.perproc_map_manual(map3d, offset=offset, factor=factor) self.set_custom_geometry_by_array(map3d, dx)
def add_material(self, material, density_map): if not isinstance(material, Material): log_and_raise_error(logger, "Cannot add material %s. It is not an instance of Material." % str(material)) if density_map.shape != self._shape: log_and_raise_error(logger, "Cannot add material. Density map has incompatible shape: %s. Should be %s." % (str(density_map.shape), str(self._shape))) self.materials.append(material) self.density_maps.append(density_map)
def set_custom_geometry_by_h5file(self, map3d_filename, map3d_dataset, dx): """ Load map from dataset in HDF5 file If a material is defined (``material_type`` is not ``None``) the absolute values of the map will be rescaled by the complex refractive index of the material. If no material is defined (``material_type=None``) the map will be casted to complex values and used without any rescaling. Args: :map3d_filename (str): Location of the HDF5 file that contains the map data :map3d_dataset (str): Dataset location in the file. The dataset must have three equal dimensions of float values. :dx: Grid spacing in unit meter """ import h5py with h5py.File(map3d_filename, "r") as f: if map3d_dataset is not None: ds = map3d_dataset elif len(f.keys()) == 1: ds = f.keys()[0] else: log_and_raise_error( logger, "No dataset specified where to find the map.") if len(f[ds].shape) == 4: map3d = numpy.array(f[ds][:, :, :, :]) elif len(f[ds].shape) == 3: map3d = numpy.array([f[ds][:, :, :]]) else: log_and_raise_error( logger, "Dataset has %i dimensions but it has to have either 3 or 4." % len(f[ds].shape)) return self.set_custom_geometry_by_array(map3d, dx)
def set_custom_geometry_by_h5file(self, map3d_filename, map3d_dataset, dx): """ Load map from dataset in HDF5 file If a material is defined (``material_type`` is not ``None``) the absolute values of the map will be rescaled by the complex refractive index of the material. If no material is defined (``material_type=None``) the map will be casted to complex values and used without any rescaling. Args: :map3d_filename (str): Location of the HDF5 file that contains the map data :map3d_dataset (str): Dataset location in the file. The dataset must have three equal dimensions of float values. :dx: Grid spacing in unit meter """ import h5py with h5py.File(map3d_filename, "r") as f: if map3d_dataset is not None: ds = map3d_dataset elif len(f.keys()) == 1: ds = f.keys()[0] else: log_and_raise_error(logger, "No dataset specified where to find the map.") if len(f[ds].shape) == 4: map3d = numpy.array(f[ds][:, :, :, :]) elif len(f[ds].shape) == 3: map3d = numpy.array([f[ds][:, :, :]]) else: log_and_raise_error( logger, "Dataset has %i dimensions but it has to have either 3 or 4." % len(f[ds].shape) ) return self.set_custom_geometry_by_array(map3d, dx)
def generate_qmap_3d(self, wavelength, qn=None, qmax=None, extrinsic_rotation=None, order='xyz'): if qn is None and qmax is None: qn = max([self._nx, self._ny]) qmax = self.get_q_max(wavelength, pos="edge") elif qn is not None and qmax is not None: pass else: log_and_raise_error(logger, "Either none or both optional arguments qn and qmax have to be passed to this function.") return return condor.utils.scattering_vector.generate_qmap_3d(qn=qn, qmax=qmax, extrinsic_rotation=extrinsic_rotation, order=order)
def get_next_number_of_particles(self): """ Iterate the number of partices """ if self.arrival == "random": return int(numpy.random.poisson(self.number)) elif self.arrival == "synchronised": return int(numpy.round(self.number)) else: log_and_raise_error(logger, "self.arrival=%s is invalid. Has to be either \'synchronised\' or \'random\'." % self.arrival)
def __init__(self, wavelength, focus_diameter, pulse_energy, profile_model=None, pulse_energy_variation=None, pulse_energy_spread=None, pulse_energy_variation_n=None, polarization="ignore"): self.photon = Photon(wavelength=wavelength) self.pulse_energy_mean = pulse_energy self.set_pulse_energy_variation(pulse_energy_variation, pulse_energy_spread, pulse_energy_variation_n) self.profile = Profile(model=profile_model, focus_diameter=focus_diameter) if polarization not in ["vertical", "horizontal", "unpolarized", "ignore"]: log_and_raise_error(logger, "polarization = \"%s\" is an invalid input for initialization of Source instance.") return self.polarization = polarization log_debug(logger, "Source configured")
def _init_mask(self, mask, mask_is_cxi_bitmask, mask_filename, mask_dataset, nx, ny, x_gap_size_in_pixel, y_gap_size_in_pixel, cx_hole, cy_hole, hole_diameter_in_pixel): if mask is not None or (mask_filename is not None and mask_dataset is not None): if mask is not None: # Copy mask from array self._mask = numpy.array(mask, dtype=numpy.uint16) else: # Read mask from file import h5py with h5py.File(mask_filename, "r") as f: self._mask = numpy.array(f[mask_dataset][:, :], dtype=numpy.uint16) if not mask_is_cxi_bitmask: # Convert maskt to CXI bit format self._mask = (self._mask == 0) * PixelMask.PIXEL_IS_MISSING elif nx is not None and ny is not None: # Initialise empty mask self._mask = numpy.zeros(shape=(int(ny + y_gap_size_in_pixel), int(nx + x_gap_size_in_pixel)), dtype=numpy.uint16) else: log_and_raise_error( logger, r"Either 'mask' or 'nx' and 'ny' have to be specified.") sys.exit(1) self._nx = self._mask.shape[1] self._ny = self._mask.shape[0] # Mask out pixels in gaps if y_gap_size_in_pixel > 0: cy = int(numpy.ceil((self._ny - 1) / 2.)) gy = int(numpy.round(y_gap_size_in_pixel)) self._mask[cy - gy / 2:cy - gy / 2 + gy, :] |= PixelMask.PIXEL_IS_MISSING if x_gap_size_in_pixel > 0: cx = int(numpy.ceil((self._nx - 1) / 2.)) gx = int(numpy.round(x_gap_size_in_pixel)) self._mask[:, cx - gx / 2:cx - gx / 2 + gx] |= PixelMask.PIXEL_IS_MISSING # Mask out pixels in hole if hole_diameter_in_pixel > 0: if cx_hole is None: cx_hole = (self._nx - 1) / 2. if cy_hole is None: cy_hole = (self._ny - 1) / 2. Y, X = numpy.indices((self._ny, self._nx), dtype=numpy.float64) X = X - cx_hole Y = Y - cy_hole R = numpy.sqrt(X**2 + Y**2) tmp = R <= hole_diameter_in_pixel / 2.0 if tmp.sum() > 0: self._mask[tmp] |= PixelMask.PIXEL_IS_MISSING
def __init__(self, pdb_filename = None, pdb_id = None, atomic_numbers = None, atomic_positions = None, rotation_values = None, rotation_formalism = None, rotation_mode = "extrinsic", number = 1., arrival = "synchronised", position = None, position_variation = None, position_spread = None, position_variation_n = None): try: import spsim except Exception,e: print str(e) log_and_raise_error(logger, "Cannot import spsim module. This module is necessary to simulate diffraction for particle model of discrete atoms. Please install spsim from https://github.com/FilipeMaia/spsim and try again.") return
def set_atomic_concentration(self, element, relative_concentration): r""" Set the concentration of a given atomic species Args: :element (str): Atomic species (e.g. ``'H'`` for hydrogen) :relative_concentration (float): Relative quantity of atoms of the given atomic species with respect to the others (e.g. for water: hydrogen concentration ``2.``, oxygen concentration ``1.``) """ if element not in atomic_names: log_and_raise_error(logger, "Cannot add element \"%s\". Invalid name." % element) self._atomic_composition[element] = relative_concentration
def get_resolution(self, wavelength = None, cx = None, cy = None, pos="corner", convention="full_period"): if wavelength is None: wavelength = self.source.photon.get_wavelength() dx = self.detector.get_resolution_element_x(wavelength, cx=cx, cy=cy) dy = self.detector.get_resolution_element_y(wavelength, cx=cx, cy=cy) dxdy = numpy.array([dx, dy]) if convention == "full_period": return dxdy*2 elif convention == "half_period": return dxdy else: log_and_raise_error(logger, "Invalid input: convention=%s. Must be either \"full_period\" or \"half_period\"." % convention) return
def add_material(self, material, density_map): if not isinstance(material, Material): log_and_raise_error( logger, "Cannot add material %s. It is not an instance of Material." % str(material)) if density_map.shape != self._shape: log_and_raise_error( logger, "Cannot add material. Density map has incompatible shape: %s. Should be %s." % (str(density_map.shape), str(self._shape))) self.materials.append(material) self.density_maps.append(density_map)
def set_atomic_concentration(self, element, relative_concentration): r""" Set the concentration of a given atomic species Args: :element (str): Atomic species (e.g. ``'H'`` for hydrogen) :relative_concentration (float): Relative quantity of atoms of the given atomic species with respect to the others (e.g. for water: hydrogen concentration ``2.``, oxygen concentration ``1.``) """ if element not in atomic_names: log_and_raise_error( logger, "Cannot add element \"%s\". Invalid name." % element) self._atomic_composition[element] = relative_concentration
def _get_next_pulse_energy(self): p = self._pulse_energy_variation.get(self.pulse_energy_mean) # Non-random if self._pulse_energy_variation._mode in [None,"range"]: if p <= 0: log_and_raise_error(logger, "Pulse energy smaller-equals zero. Change your configuration.") else: return p # Random else: if p <= 0.: log_warning(logger, "Pulse energy smaller-equals zero. Try again.") self._get_next_pulse_energy() else: return p
def set_atoms_from_arrays(self, atomic_numbers, atomic_positions): r""" Specify atomic positions from atomic numbers and atomic positions Args: :atomic_numbers (array): Integer array of atomic numbers specifies the element species of each atom. Array shape: (:math:`N`,) with :math:`N` denoting the number of atoms. :atomic_position (array): Float array of atomic positions [:math:`x`, :math:`y`, :math:`z`] in unit meter. Array shape: (:math:`N`, 3,) with :math:`N` denoting the number of atoms """ N1 = len(atomic_numbers) N2 = len(atomic_positions) if N1 != N2: log_and_raise_error(logger, "Cannot set atoms. atomic_numbers and atomic_positions have to have the same length") self._atomic_positions = numpy.array(atomic_positions) self._atomic_numbers = numpy.array(atomic_numbers)
def _get_next_diameter(self): d = self._diameter_variation.get(self.diameter_mean) # Non-random diameter if self._diameter_variation._mode in [None,"range"]: if d <= 0: log_and_raise_error(logger,"Sample diameter smaller-equals zero. Change your configuration.") else: return d # Random diameter else: if d <= 0.: log_warning(logger, "Sample diameter smaller-equals zero. Try again.") return self._get_next_diameter() else: return d
def _get_next_flattening(self): f = self._flattening_variation.get(self.flattening_mean) # Non-random if self._flattening_variation._mode in [None, "range"]: if f <= 0: log_and_raise_error(logger, "Spheroid flattening smaller-equals zero. Change your configuration.") else: return f # Random else: if f <= 0.: log_warning(logger, "Spheroid flattening smaller-equals zero. Try again.") return self._get_next_flattening() else: return f
def set_alignment(self, rotation_values, rotation_formalism, rotation_mode): """ Set rotation scheme of the partice Args: :rotation_values: Array of rotation parameters. For simulating patterns of many shots this can be also a sequence of rotation parameters. Input ``None`` for no rotation and for random rotation formalisms. For more documentation see :class:`condor.utils.rotation.Rotations` (default ``None``) :rotation_mode (str): If the rotation shall be assigned to the particle choose ``\'extrinsic\'``. Choose ``\'intrinsic\'`` if the coordinate system shall be rotated (default ``\'extrinsic\'``) """ # Check input if rotation_mode not in ["extrinsic","intrinsic"]: log_and_raise_error(logger, "%s is not a valid rotation mode for alignment." % rotation_mode) sys.exit(1) self._rotation_mode = rotation_mode self._rotations = condor.utils.rotation.Rotations(values=rotation_values, formalism=rotation_formalism)
def _get_material_conf(self): conf = {} for m_i in self.materials: conf_i = m_i.get_conf() if isinstance(m_i, AtomDensityMaterial): conf_i["electron_density"] = None elif isinstance(m_i, ElectronDensityMaterial): conf_i["material_type"] = None conf_i["massdensity"] = None conf_i["atomic_composition"] = None else: log_and_raise_error(logger, "Material has the wrong class: %s" % str(m_i)) for k,v in conf_i.items(): if k not in conf: conf[k] = [] conf[k].append(conf_i[k]) return conf
def __init__(self, material_type, massdensity = None, atomic_composition = None): AbstractMaterial.__init__(self) self.clear_atomic_composition() if atomic_composition is not None and massdensity is not None and (material_type is None or material_type == "custom"): for element,concentration in atomic_composition.items(): self.set_atomic_concentration(element, concentration) self.massdensity = massdensity elif material_type is not None and atomic_composition is None and massdensity is None: for element, concentration in MaterialType.atomic_compositions[material_type].items(): self.set_atomic_concentration(element, concentration) self.massdensity = MaterialType.mass_densities[material_type] else: log_and_raise_error(logger, "Invalid arguments in Material initialization.")
def get_intensity(self, position, unit="ph/m2", pulse_energy=None): """ Calculate the intensity at a given position in the focus Args: :position: Coordinates [*x*, *y*, *z*] of the position where the intensity shall be calculated Kwargs: :unit (str): Intensity unit (default ``\'ph/m2\'``) *Choose one of the following options:* - ``\'ph/m2\'`` - ``\'J/m2\'`` - ``\'J/um2\'`` - ``\'mJ/um2\'`` - ``\'ph/um2\'`` :pulse_energy (float): Pulse energy of that particular pulse in unit Joule. If ``None`` the mean of the pulse energy will be used (default ``None``) """ # Assuming # 1) Radially symmetric profile that is invariant along the beam axis within the sample volume # 2) The variation of intensity are on much larger scale than the dimension of the particle size (i.e. flat wavefront) r = numpy.sqrt(position[1]**2 + position[2]**2) I = (self.profile.get_radial())(r) * (pulse_energy if pulse_energy is not None else self.pulse_energy_mean) if unit == "J/m2": pass elif unit == "ph/m2": I /= self.photon.get_energy() elif unit == "J/um2": I *= 1.E-12 elif unit == "mJ/um2": I *= 1.E-9 elif unit == "ph/um2": I /= self.photon.get_energy() I *= 1.E-12 else: log_and_raise_error(logger, "%s is not a valid unit." % unit) return return I
def set_atoms_from_arrays(self, atomic_numbers, atomic_positions): r""" Specify atomic positions from atomic numbers and atomic positions Args: :atomic_numbers (array): Integer array of atomic numbers specifies the element species of each atom. Array shape: (:math:`N`,) with :math:`N` denoting the number of atoms. :atomic_position (array): Float array of atomic positions [:math:`x`, :math:`y`, :math:`z`] in unit meter. Array shape: (:math:`N`, 3,) with :math:`N` denoting the number of atoms """ N1 = len(atomic_numbers) N2 = len(atomic_positions) if N1 != N2: log_and_raise_error( logger, "Cannot set atoms. atomic_numbers and atomic_positions have to have the same length" ) self._atomic_positions = numpy.array(atomic_positions) self._atomic_numbers = numpy.array(atomic_numbers)
def get_intensity(self, position, unit = "ph/m2", pulse_energy = None): """ Calculate the intensity at a given position in the focus Args: :position: Coordinates [*x*, *y*, *z*] of the position where the intensity shall be calculated Kwargs: :unit (str): Intensity unit (default ``\'ph/m2\'``) *Choose one of the following options:* - ``\'ph/m2\'`` - ``\'J/m2\'`` - ``\'J/um2\'`` - ``\'mJ/um2\'`` - ``\'ph/um2\'`` :pulse_energy (float): Pulse energy of that particular pulse in unit Joule. If ``None`` the mean of the pulse energy will be used (default ``None``) """ # Assuming # 1) Radially symmetric profile that is invariant along the beam axis within the sample volume # 2) The variation of intensity are on much larger scale than the dimension of the particle size (i.e. flat wavefront) r = numpy.sqrt(position[1]**2 + position[2]**2) I = (self.profile.get_radial())(r) * (pulse_energy if pulse_energy is not None else self.pulse_energy_mean) if unit == "J/m2": pass elif unit == "ph/m2": I /= self.photon.get_energy() elif unit == "J/um2": I *= 1.E-12 elif unit == "mJ/um2": I *= 1.E-9 elif unit == "ph/um2": I /= self.photon.get_energy() I *= 1.E-12 else: log_and_raise_error(logger, "%s is not a valid unit." % unit) return return I
def _init_mask(self, mask, mask_is_cxi_bitmask, mask_filename, mask_dataset, nx, ny, x_gap_size_in_pixel, y_gap_size_in_pixel, cx_hole, cy_hole, hole_diameter_in_pixel): if mask is not None or (mask_filename is not None and mask_dataset is not None): if mask is not None: # Copy mask from array self._mask = numpy.array(mask, dtype=numpy.uint16) else: # Read mask from file import h5py with h5py.File(mask_filename,"r") as f: self._mask = numpy.array(f[mask_dataset][:,:], dtype=numpy.uint16) if not mask_is_cxi_bitmask: # Convert maskt to CXI bit format self._mask = (self._mask == 0) * PixelMask.PIXEL_IS_MISSING elif nx is not None and ny is not None: # Initialise empty mask self._mask = numpy.zeros(shape=(int(ny+y_gap_size_in_pixel), int(nx+x_gap_size_in_pixel)),dtype=numpy.uint16) else: log_and_raise_error(logger, r"Either 'mask' or 'nx' and 'ny' have to be specified.") sys.exit(1) self._nx = self._mask.shape[1] self._ny = self._mask.shape[0] # Mask out pixels in gaps if y_gap_size_in_pixel > 0: cy = int(numpy.ceil((self._ny-1)/2.)) gy = int(numpy.round(y_gap_size_in_pixel)) self._mask[cy-gy/2:cy-gy/2+gy,:] |= PixelMask.PIXEL_IS_MISSING if x_gap_size_in_pixel > 0: cx = int(numpy.ceil((self._nx-1)/2.)) gx = int(numpy.round(x_gap_size_in_pixel)) self._mask[:,cx-gx/2:cx-gx/2+gx] |= PixelMask.PIXEL_IS_MISSING # Mask out pixels in hole if hole_diameter_in_pixel > 0: if cx_hole is None: cx_hole = (self._nx-1)/2. if cy_hole is None: cy_hole = (self._ny-1)/2. Y,X = numpy.indices((self._ny,self._nx), dtype=numpy.float64) X = X-cx_hole Y = Y-cy_hole R = numpy.sqrt(X**2 + Y**2) tmp = R<=hole_diameter_in_pixel/2.0 if tmp.sum() > 0: self._mask[tmp] |= PixelMask.PIXEL_IS_MISSING
def set_custom_geometry_by_array(self, map3d, dx): """ Set map from numpy array Args: :map3d (array): 4D numpy array (material index, z, y, x) of float values. If a material is defined (material not ``None``) the values of the map scale the complex refractive index of the material. If no material is defined (materials is ``None``) the map will be casted to complex values and used without any rescaling. :dx (float): Grid spacing in unit meter """ # Check shape s = numpy.array(map3d.shape) if numpy.any(s[-3:] != s[-1]): log_and_raise_error( logger, "Condor only accepts maps with equal spatial dimensions. Current shape is: %s" % str(s[-3:])) if self.materials is None: # Complex map(s) = refractive index map # Check input if len(s) == 3: map3d = [map3d] if len(s) < 3 or len(s) > 4: log_and_raise_error( logger, "map3d has %i dimensions but should have 3 or 4." % len(s)) return # Load map(s) _map3d = numpy.asarray(map3d) else: # Real map(s) to be scaled by material's complext refractive index # Check input if len(s) not in [3, 4]: log_and_raise_error( logger, "map3d has %i dimensions but it has to have either 3 or 4." % len(s)) return # Load map(s) if len(s) == 3: n_mat = len(self.materials) s = numpy.array([n_mat] + list(s)) _map3d = numpy.array(n_mat * [map3d], dtype=numpy.float64) else: if s[0] != len(self.materials): log_and_raise_error( logger, "The first dimension of the map (%i) does not equal the number of specified materials (%i)." % (s[0], len(self.materials))) return _map3d = numpy.asarray(map3d, dtype=numpy.float64) self._map3d_orig = _map3d self._dx_orig = dx self._set_cache(_map3d, dx, geometry="custom")
def _get_next_pulse_energy(self): p = self._pulse_energy_variation.get(self.pulse_energy_mean) # Non-random if self._pulse_energy_variation._mode in [None, "range"]: if p <= 0: log_and_raise_error( logger, "Pulse energy smaller-equals zero. Change your configuration." ) else: return p # Random else: if p <= 0.: log_warning(logger, "Pulse energy smaller-equals zero. Try again.") self._get_next_pulse_energy() else: return p
def __init__(self, pdb_filename = None, pdb_id = None, atomic_numbers = None, atomic_positions = None, rotation_values = None, rotation_formalism = None, rotation_mode = "extrinsic", number = 1., arrival = "synchronised", position = None, position_variation = None, position_spread = None, position_variation_n = None): try: import spsim except Exception as e: print(str(e)) log_and_raise_error(logger, "Cannot import spsim module. This module is necessary to simulate diffraction for particle model of discrete atoms. Please install spsim from https://github.com/FilipeMaia/spsim and try again.") return # Initialise base class AbstractParticle.__init__(self, rotation_values=rotation_values, rotation_formalism=rotation_formalism, rotation_mode=rotation_mode, number=number, arrival=arrival, position=position, position_variation=position_variation, position_spread=position_spread, position_variation_n=position_variation_n) self._atomic_positions = None self._atomic_numbers = None self._pdb_filename = None self._diameter_mean = None if pdb_filename is not None: log_debug(logger, "Attempt reading atoms from PDB file %s." % pdb_filename) if (pdb_id is not None or atomic_numbers is not None or atomic_positions is not None): log_and_raise_error(logger, "Atom configuration is ambiguous. pdb_filename is specified but also at least one of the following arguments: atomic_numbers, atomic_positions, pdb_id.") sys.exit(1) elif not os.path.isfile(pdb_filename): log_and_raise_error(logger, "Cannot initialize particle model. PDB file %s does not exist." % pdb_filename) sys.exit(1) else: self.set_atoms_from_pdb_file(pdb_filename) elif pdb_id is not None: log_debug(logger, "Attempt fetching PDB entry of ID=%s" % pdb_id) if (atomic_numbers is not None or atomic_positions is not None): log_and_raise_error(logger, "Atom configuration is ambiguous. pdb_id is specified but also at least one of the following arguments: atomic_numbers, atomic_positions.") sys.exit(1) else: self.set_atoms_from_pdb_id(pdb_id) elif atomic_numbers is not None and atomic_positions is not None: log_debug(logger, "Attempt reading atoms from lists/attays.") self.set_atoms_from_arrays(atomic_numbers, atomic_positions) else: log_and_raise_error(logger, "Cannot initialise particle model. The atomic positions have to be specified either by a pdb_filename, pdb_id or by atomic_numbers and atomic_positions.")
def _get_next_flattening(self): f = self._flattening_variation.get(self.flattening_mean) # Non-random if self._flattening_variation._mode in [None, "range"]: if f <= 0: log_and_raise_error( logger, "Spheroid flattening smaller-equals zero. Change your configuration." ) else: return f # Random else: if f <= 0.: log_warning( logger, "Spheroid flattening smaller-equals zero. Try again.") return self._get_next_flattening() else: return f
def __init__(self, source, particles, detector): self.source = source for n,p in particles.items(): if n.startswith("particle_sphere"): if not isinstance(p, condor.particle.ParticleSphere): log_and_raise_error(logger, "Particle %s is not a condor.particle.ParticleSphere instance." % n) elif n.startswith("particle_spheroid"): if not isinstance(p, condor.particle.ParticleSpheroid): log_and_raise_error(logger, "Particle %s is not a condor.particle.ParticleSpheroid instance." % n) elif n.startswith("particle_map"): if not isinstance(p, condor.particle.ParticleMap): log_and_raise_error(logger, "Particle %s is not a condor.particle.ParticleMap instance." % n) elif n.startswith("particle_atoms"): if not isinstance(p, condor.particle.ParticleAtoms): log_and_raise_error(logger, "Particle %s is not a condor.particle.ParticleAtoms instance." % n) else: log_and_raise_error(logger, "The particle model name %s is invalid. The name has to start with either particle_sphere, particle_spheroid, particle_map or particle_atoms.") self.particles = particles self.detector = detector self._qmap_cache = {}
def get_mask(self,intensities=None, boolmask=False): """ Return mask. The mask has information about the status of each individual detector pixel. The output can be either a CXI bitmask (default) or a boolean mask For further information and the full bitcode go to :class:`condor.utils.pixelmask.PixelMask` Kwargs: :intensities: Numpy array of photon intensities for masking saturated pixels (default ``None``) :boolmask (bool): If ``True`` the output will be a boolean array. Mask values are converted to ``True`` if no bit is set and to ``False`` otherwise """ if intensities is not None: if not condor.utils.testing.same_shape(intensities, self._mask): log_and_raise_error(logger, "Intensities and mask do not have the same shape") M = self._mask.copy() if self.saturation_level is not None and intensities is not None: M[intensities >= self.saturation_level] |= PixelMask.PIXEL_IS_SATURATED if boolmask: return numpy.array(M == 0,dtype="bool") else: return M
def set_custom_geometry_by_array(self, map3d, dx): """ Set map from numpy array Args: :map3d (array): 4D numpy array (material index, z, y, x) of float values. If a material is defined (material not ``None``) the values of the map scale the complex refractive index of the material. If no material is defined (materials is ``None``) the map will be casted to complex values and used without any rescaling. :dx (float): Grid spacing in unit meter """ # Check shape s = numpy.array(map3d.shape) if numpy.any(s[-3:] != s[-1]): log_and_raise_error( logger, "Condor only accepts maps with equal spatial dimensions. Current shape is: %s" % str(s[-3:]) ) if self.materials is None: # Complex map(s) = refractive index map # Check input if len(s) == 3: map3d = [map3d] if len(s) < 3 or len(s) > 4: log_and_raise_error(logger, "map3d has %i dimensions but should have 3 or 4." % len(s)) return # Load map(s) _map3d = numpy.asarray(map3d) else: # Real map(s) to be scaled by material's complext refractive index # Check input if len(s) not in [3, 4]: log_and_raise_error(logger, "map3d has %i dimensions but it has to have either 3 or 4." % len(s)) return # Load map(s) if len(s) == 3: n_mat = len(self.materials) s = numpy.array([n_mat] + list(s)) _map3d = numpy.array(n_mat * [map3d], dtype=numpy.float64) else: if s[0] != len(self.materials): log_and_raise_error( logger, "The first dimension of the map (%i) does not equal the number of specified materials (%i)." % (s[0], len(self.materials)), ) return _map3d = numpy.asarray(map3d, dtype=numpy.float64) self._map3d_orig = _map3d self._dx_orig = dx self._set_cache(_map3d, dx, geometry="custom")
def __init__(self, pdb_filename=None, pdb_id=None, atomic_numbers=None, atomic_positions=None, rotation_values=None, rotation_formalism=None, rotation_mode="extrinsic", number=1., arrival="synchronised", position=None, position_variation=None, position_spread=None, position_variation_n=None): try: import spsim except Exception, e: print str(e) log_and_raise_error( logger, "Cannot import spsim module. This module is necessary to simulate diffraction for particle model of discrete atoms. Please install spsim from https://github.com/FilipeMaia/spsim and try again." ) return
def add_material(self, material_type, massdensity, atomic_composition, electron_density): """ Initialise and add the AtomDensityMaterial / ElectronDensityMaterial class instance to the particle Args: :material_type (str): See :class:`condor.utils.material.AtomDensityMaterial` :massdensity (float): See :class:`condor.utils.material.AtomDensityMaterial` :atomic_composition (dict): See :class:`condor.utils.material.AtomDensityMaterial` :electron_density (float): See :class:`condor.utils.material.ElectronDensityMaterial` """ if electron_density is None: self.materials.append(AtomDensityMaterial(material_type=material_type, massdensity=massdensity, atomic_composition=atomic_composition)) else: if massdensity is not None or atomic_composition is not None: log_and_raise_error(logger, r"An electron density is defined so material_type, massdensity and atomic_composition have to be all 'None'.") return if material_type != "custom": log_and_raise_error(logger, r"An electron density is defined, so material_type must be \'custom\' but is %s." % material_type) return self.materials.append(ElectronDensityMaterial(electron_density=electron_density))
def __init__(self, material_type, massdensity=None, atomic_composition=None): AbstractMaterial.__init__(self) self.clear_atomic_composition() if atomic_composition is not None and massdensity is not None and ( material_type is None or material_type == "custom"): for element, concentration in atomic_composition.items(): self.set_atomic_concentration(element, concentration) self.massdensity = massdensity elif material_type is not None and atomic_composition is None and massdensity is None: for element, concentration in MaterialType.atomic_compositions[ material_type].items(): self.set_atomic_concentration(element, concentration) self.massdensity = MaterialType.mass_densities[material_type] else: log_and_raise_error( logger, "Invalid arguments in Material initialization.")
def set_custom_geometry_by_mrcfile(self, filename, offset=None, factor=None): """ Read map from the MRC file (CCP4 file format, see http://www.ccp4.ac.uk/html/maplib.html). The map will be preprocessed by applying an offset and rescaling and by padding the water background with zeros. Finally, the avereage value of the map will be rescaled by the refractive index of the associated material. Args: :filename (str): Filename of MRC file. :offset (float): Offset value of the map (MAP = (EM_DATA + OFFSET) X FACTOR) :factor (float): Rescale factor of the map (MAP = (EM_DATA + OFFSET) X FACTOR) """ map3d, dx = condor.utils.emdio.read_map(filename) if offset is None and factor is None: ed_water = condor.utils.material.AtomDensityMaterial( material_type="water").get_electron_density() if len(self.materials) > 1: log_and_raise_error( logger, "More than one material defined. This is incompatible with automatic scaling of an EMD map." ) sys.exit(1) ed_particle = self.materials[0].get_electron_density() map3d = condor.utils.emdio.preproc_map_auto( map3d, ed_water=ed_water, ed_particle=ed_particle) else: map3d = condor.utils.emdio.perproc_map_manual(map3d, offset=offset, factor=factor) self.set_custom_geometry_by_array(map3d, dx)
def get_new_map(self, O, dx_required, dx_suggested): """ Return new map with given parameters Args: :O (dict): Parameter dictionary as returned from :meth:`condor.particle.particle_map.get_next` :dx_required (float): Required resolution (grid spacing) of the map. An error is raised if the resolution of the map has too low resolution :dx_suggested (float): Suggested resolution (grid spacing) of the map. If the map has a very high resolution it will be interpolated to a the suggested resolution value """ if O["geometry"] in ["icosahedron", "sphere", "spheroid", "cube"]: if not self._is_map_in_cache(O, dx_required): dx = dx_suggested n_mat = len(self.materials) if O["geometry"] == "icosahedron": m_tmp = self._get_map_icosahedron(O["diameter"] / 2., dx) elif O["geometry"] == "spheroid": a = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_a( O["diameter"], O["flattening"]) c = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_c( O["diameter"], O["flattening"]) m_tmp = self._get_map_spheroid(a, c, dx) elif O["geometry"] == "sphere": m_tmp = self._get_map_sphere(O["diameter"] / 2., dx) elif O["geometry"] == "cube": m_tmp = self._get_map_cube(O["diameter"] / 2., dx) else: log_and_raise_error( logger, "Particle map geometry \"%s\" is not implemented. Change your configuration and try again." % O["geometry"]) sys.exit(1) m = numpy.array(n_mat * [m_tmp]) self._set_cache(map3d=m, dx=dx, geometry=O["geometry"], diameter=O["diameter"], flattening=(None if O["geometry"] != "spheroid" else O["flattening"])) else: log_debug( logger, "No need for calculating a new map. Reading map from cache." ) m = self._cache["map3d"] dx = self._cache["dx"] elif O["geometry"] == "custom": rescale_factor = O["diameter"] / self.diameter_mean dx_rescaled = self._cache["dx"] * rescale_factor # Current map too coarsely sampled? if (dx_rescaled / dx_required > 1.) and not numpy.isclose( dx_rescaled / dx_required, 1.): # Cached map (original) also too coarsely sampled? if self._dx_orig / dx_required > 1. and not numpy.isclose( self._dx_orig / dx_required, 1.): # Not fine enough -> exit log_and_raise_error( logger, "Resolution of given custom map is insufficient for simulation. Required is at most %e m vs. provided %e m." % (dx_required, self._dx_orig)) sys.exit(1) else: # Change back to original fine map self._set_cache(map3d=self._map3d_orig, dx=self._dx_orig, geometry="custom") # Can we downsample current map? # MAX: We would do this only for performance reasons but have not found a good way of downsampling without introducing artifacts #if (dx_suggested/dx_rescaled >= 2.) and (dx_suggested/self._dx_orig >= 2.) and ENABLE_MAP_INTERPOLATION: # print "ENABLE_MAP_INTERPOLATION=%i" % ENABLE_MAP_INTERPOLATION # N1 = self._map3d_orig.shape[0] # m1 = numpy.zeros(shape=(N1,N1,N1), dtype=numpy.float64) # m1[:self._map3d_orig.shape[0],:self._map3d_orig.shape[0],:self._map3d_orig.shape[0]] = self._map3d_orig[:,:,:] # fm1 = numpy.fft.fftshift(numpy.fft.ifftn(m1)) # N1 = m1.shape[0] # N2 = int(numpy.ceil(N1 * self._dx_orig / dx_suggested)) # x1 = numpy.linspace(-0.5,0.5,N2)*(1-0.5/N2) # Z,Y,X = numpy.meshgrid(x1,x1,x1,indexing="ij") # coords = numpy.array([[z,y,x] for z,y,x in zip(Z.ravel(),Y.ravel(),X.ravel())]) # m2 = abs(numpy.fft.fftshift(condor.utils.nfft.nfft(fm1,coords).reshape((N2,N2,N2)))) # #from pylab import * # #imsave("m1.png", m1.sum(0)) # #imsave("m2.png", m2.sum(0)) # self._dx = self._dx_orig * float(N1)/float(N2) # self._map3d = m2 / m2.sum() * m1.sum() m = self._cache["map3d"] dx = rescale_factor * self._cache["dx"] return m, dx
class ParticleAtoms(AbstractParticle): """ Class for a particle model *Model:* Discrete atomic positions Kwargs: :pdb_filename (str): See :meth:`set_atoms_from_pdb_file` (default ``None``) :pdb_id (str): See :meth:`set_atoms_from_pdb_id` (default ``None``) :atomic_numbers (array): See :meth:`set_atoms_from_arrays` (default ``None``) :atomic_positions (array): See :meth:`set_atoms_from_arrays` (default ``None``) .. note:: The atomic positions have to be specified either by a ``pdb_filename`` or by ``atomic_numbers`` and ``atomic_positions``. :rotation_values (array): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_alignment` (default ``None``) :rotation_formalism (str): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_alignment` (default ``None``) :rotation_mode (str): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_alignment` (default ``None``) :number (float): Expectation value for the number of particles in the interaction volume. (defaukt ``1.``) :arrival (str): Arrival of particles at the interaction volume can be either ``'random'`` or ``'synchronised'``. If ``sync`` at every event the number of particles in the interaction volume equals the rounded value of ``number``. If ``'random'`` the number of particles is Poissonian and ``number`` is the expectation value. (default ``'synchronised'``) :position (array): See :class:`condor.particle.particle_abstract.AbstractParticle` (default ``None``) :position_variation (str): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_position_variation` (default ``None``) :position_spread (float): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_position_variation` (default ``None``) :position_variation_n (int): See :meth:`condor.particle.particle_abstract.AbstractParticle.set_position_variation` (default ``None``) """ def __init__(self, pdb_filename=None, pdb_id=None, atomic_numbers=None, atomic_positions=None, rotation_values=None, rotation_formalism=None, rotation_mode="extrinsic", number=1., arrival="synchronised", position=None, position_variation=None, position_spread=None, position_variation_n=None): try: import spsim except Exception, e: print str(e) log_and_raise_error( logger, "Cannot import spsim module. This module is necessary to simulate diffraction for particle model of discrete atoms. Please install spsim from https://github.com/FilipeMaia/spsim and try again." ) return # Initialise base class AbstractParticle.__init__(self, rotation_values=rotation_values, rotation_formalism=rotation_formalism, rotation_mode=rotation_mode, number=number, arrival=arrival, position=position, position_variation=position_variation, position_spread=position_spread, position_variation_n=position_variation_n) self._atomic_positions = None self._atomic_numbers = None self._pdb_filename = None self._diameter_mean = None if pdb_filename is not None: log_debug(logger, "Attempt reading atoms from PDB file %s." % pdb_filename) if (pdb_id is not None or atomic_numbers is not None or atomic_positions is not None): log_and_raise_error( logger, "Atom configuration is ambiguous. pdb_filename is specified but also at least one of the following arguments: atomic_numbers, atomic_positions, pdb_id." ) sys.exit(1) elif not os.path.isfile(pdb_filename): log_and_raise_error( logger, "Cannot initialize particle model. PDB file %s does not exist." % pdb_filename) sys.exit(1) else: self.set_atoms_from_pdb_file(pdb_filename) elif pdb_id is not None: log_debug(logger, "Attempt fetching PDB entry of ID=%s" % pdb_id) if (atomic_numbers is not None or atomic_positions is not None): log_and_raise_error( logger, "Atom configuration is ambiguous. pdb_id is specified but also at least one of the following arguments: atomic_numbers, atomic_positions." ) sys.exit(1) else: self.set_atoms_from_pdb_id(pdb_id) elif atomic_numbers is not None and atomic_positions is not None: log_debug(logger, "Attempt reading atoms from lists/attays.") self.set_atoms_from_arrays(atomic_numbers, atomic_positions) else: log_and_raise_error( logger, "Cannot initialise particle model. The atomic positions have to be specified either by a pdb_filename, pdb_id or by atomic_numbers and atomic_positions." )
def __init__( self, geometry, diameter=None, diameter_variation=None, diameter_spread=None, diameter_variation_n=None, dx=None, map3d=None, map3d_filename=None, map3d_dataset=None, emd_id=None, rotation_values=None, rotation_formalism=None, rotation_mode="extrinsic", flattening=0.75, number=1.0, arrival="synchronised", position=None, position_variation=None, position_spread=None, position_variation_n=None, material_type=None, massdensity=None, atomic_composition=None, electron_density=None, ): # Initialise base class AbstractContinuousParticle.__init__( self, diameter=diameter, diameter_variation=diameter_variation, diameter_spread=diameter_spread, diameter_variation_n=diameter_variation_n, rotation_values=rotation_values, rotation_formalism=rotation_formalism, rotation_mode=rotation_mode, number=number, arrival=arrival, position=position, position_variation=position_variation, position_spread=position_spread, position_variation_n=position_variation_n, material_type=material_type, massdensity=massdensity, atomic_composition=atomic_composition, electron_density=electron_density, ) # Check for valid geometry if geometry not in ["icosahedron", "cube", "sphere", "spheroid", "custom"]: log_and_raise_error( logger, "Cannot initialize %s because '%s' is not a valid argument for 'geometry'." % (kwargs["geometry"], self.__class__.__name__), ) sys.exit(1) self.geometry = geometry # Has effect only for spheroids self.flattening = flattening # Init chache self._cache = {} self._dx_orig = None self._map3d_orig = None if geometry == "custom": if map3d is not None: if dx is None: log_and_raise_error( logger, "Cannot initialize custom geometry with 'map3d' but without grid spacing 'dx'." ) sys.exit(1) else: log_debug(logger, "Attempting to initialise custom geometry with 'map3d'.") if map3d_filename is not None or map3d_dataset is not None or emd_id is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_array(map3d, dx) elif map3d_filename is not None and map3d_dataset is not None: if dx is None: log_and_raise_error( logger, "You are trying to initialise the map with an HDF5 file. You also need to provide the grid spacing 'dx'", ) sys.exit(1) log_debug( logger, "Attempting to initialise custom geometry with 'map3d_filename', 'map3d_dataset' and 'dx'." ) if not map3d_filename.endswith(".h5"): log_and_raise_error(logger, "Map file is not an HDF5 file!") sys.exit(1) if map3d is not None or emd_id is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_h5file(map3d_filename, map3d_dataset, dx) elif map3d_filename is not None: if not map3d_filename.endswith(".map") and not map3d_filename.endswith(".mrc"): log_and_raise_error(logger, "Map file is not an MRC/MAP file!") sys.exit(1) self.set_custom_geometry_by_mrcfile(map3d_filename) elif emd_id is not None: log_debug(logger, "Attempting to initialise custom geometry with 'emd_id'.") if map3d_filename is not None or map3d_dataset is not None or map3d is not None or dx is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_emd_id(emd_id) if diameter is None: self.diameter_mean = self._dx_orig * self._map3d_orig.shape[-1]
def get_new_map(self, O, dx_required, dx_suggested): """ Return new map with given parameters Args: :O (dict): Parameter dictionary as returned from :meth:`condor.particle.particle_map.get_next` :dx_required (float): Required resolution (grid spacing) of the map. An error is raised if the resolution of the map has too low resolution :dx_suggested (float): Suggested resolution (grid spacing) of the map. If the map has a very high resolution it will be interpolated to a the suggested resolution value """ if O["geometry"] in ["icosahedron", "sphere", "spheroid", "cube"]: if not self._is_map_in_cache(O, dx_required): dx = dx_suggested n_mat = len(self.materials) if O["geometry"] == "icosahedron": m_tmp = self._get_map_icosahedron(O["diameter"] / 2.0, dx) elif O["geometry"] == "spheroid": a = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_a(O["diameter"], O["flattening"]) c = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_c(O["diameter"], O["flattening"]) m_tmp = self._get_map_spheroid(a, c, dx) elif O["geometry"] == "sphere": m_tmp = self._get_map_sphere(O["diameter"] / 2.0, dx) elif O["geometry"] == "cube": m_tmp = self._get_map_cube(O["diameter"] / 2.0, dx) else: log_and_raise_error( logger, 'Particle map geometry "%s" is not implemented. Change your configuration and try again.' % O["geometry"], ) sys.exit(1) m = numpy.array(n_mat * [m_tmp]) self._set_cache( map3d=m, dx=dx, geometry=O["geometry"], diameter=O["diameter"], flattening=(None if O["geometry"] != "spheroid" else O["flattening"]), ) else: log_debug(logger, "No need for calculating a new map. Reading map from cache.") m = self._cache["map3d"] dx = self._cache["dx"] elif O["geometry"] == "custom": dx_needed = O["diameter"] / self.diameter_mean * self._cache["dx"] # Map fine enough? if dx_needed / dx_required >= 1.0: if self._dx_orig / dx_required >= 1.0: # Not fine enough -> exit log_and_raise_error( logger, "Resolution of given custom map is insufficient for simulation. required %e m vs. provided %e m." % (dx_required, self._dx_orig), ) sys.exit(1) # Change back to original fine map self.set_cache(map3d=self._map3d_orig, dx=self._dx_orig, geometry="custom") # Can we downsample current map? # if (dx_suggested/dx_needed >= 2.) and (dx_suggested/self._dx_orig >= 2.) and ENABLE_MAP_INTERPOLATION: # print "ENABLE_MAP_INTERPOLATION=%i" % ENABLE_MAP_INTERPOLATION # N1 = self._map3d_orig.shape[0] # m1 = numpy.zeros(shape=(N1,N1,N1), dtype=numpy.float64) # m1[:self._map3d_orig.shape[0],:self._map3d_orig.shape[0],:self._map3d_orig.shape[0]] = self._map3d_orig[:,:,:] # fm1 = numpy.fft.fftshift(numpy.fft.ifftn(m1)) # N1 = m1.shape[0] # N2 = int(numpy.ceil(N1 * self._dx_orig / dx_suggested)) # x1 = numpy.linspace(-0.5,0.5,N2)*(1-0.5/N2) # Z,Y,X = numpy.meshgrid(x1,x1,x1,indexing="ij") # coords = numpy.array([[z,y,x] for z,y,x in zip(Z.ravel(),Y.ravel(),X.ravel())]) # m2 = abs(numpy.fft.fftshift(condor.utils.nfft.nfft(fm1,coords).reshape((N2,N2,N2)))) # #from pylab import * # #imsave("m1.png", m1.sum(0)) # #imsave("m2.png", m2.sum(0)) # self._dx = self._dx_orig * float(N1)/float(N2) # self._map3d = m2 / m2.sum() * m1.sum() m = self._cache["map3d"] dx = O["diameter"] / self.diameter_mean * self._cache["dx"] return m, dx
def __init__(self, shape): if len(shape) != 3: log_and_raise_error(logger, "%s is an invald shape for initialisation of MaterialMap.", str(shape)) self._shape = tuple(shape)
def __init__(self, geometry, diameter=None, diameter_variation=None, diameter_spread=None, diameter_variation_n=None, dx=None, map3d=None, map3d_filename=None, map3d_dataset=None, emd_id=None, rotation_values=None, rotation_formalism=None, rotation_mode="extrinsic", flattening=0.75, number=1., arrival="synchronised", position=None, position_variation=None, position_spread=None, position_variation_n=None, material_type=None, massdensity=None, atomic_composition=None, electron_density=None): # Initialise base class AbstractContinuousParticle.__init__( self, diameter=diameter, diameter_variation=diameter_variation, diameter_spread=diameter_spread, diameter_variation_n=diameter_variation_n, rotation_values=rotation_values, rotation_formalism=rotation_formalism, rotation_mode=rotation_mode, number=number, arrival=arrival, position=position, position_variation=position_variation, position_spread=position_spread, position_variation_n=position_variation_n, material_type=material_type, massdensity=massdensity, atomic_composition=atomic_composition, electron_density=electron_density) # Check for valid geometry if geometry not in [ "icosahedron", "cube", "sphere", "spheroid", "custom" ]: log_and_raise_error( logger, "Cannot initialize %s because \'%s\' is not a valid argument for \'geometry\'." % (kwargs["geometry"], self.__class__.__name__)) sys.exit(1) self.geometry = geometry # Has effect only for spheroids self.flattening = flattening # Init chache self._cache = {} self._dx_orig = None self._map3d_orig = None if geometry == "custom": if map3d is not None: if dx is None: log_and_raise_error( logger, "Cannot initialize custom geometry with \'map3d\' without known grid spacing (\'dx\')." ) sys.exit(1) else: log_debug( logger, "Attempting to initialise custom geometry with \'map3d\'." ) if map3d_filename is not None or map3d_dataset is not None or emd_id is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_array(map3d, dx) elif map3d_filename is not None and map3d_dataset is not None: if dx is None: log_and_raise_error( logger, "You are trying to initialise the map with an HDF5 file. You also need to provide the grid spacing \'dx\'" ) sys.exit(1) log_debug( logger, "Attempting to initialise custom geometry with \'map3d_filename\', \'map3d_dataset\' and \'dx\'." ) if not map3d_filename.endswith(".h5"): log_and_raise_error(logger, "Map file is not an HDF5 file!") sys.exit(1) if map3d is not None or emd_id is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_h5file(map3d_filename, map3d_dataset, dx) elif map3d_filename is not None: if not map3d_filename.endswith( ".map") and not map3d_filename.endswith(".mrc"): log_and_raise_error(logger, "Map file is not an MRC/MAP file!") sys.exit(1) self.set_custom_geometry_by_mrcfile(map3d_filename) elif emd_id is not None: log_debug( logger, "Attempting to initialise custom geometry with \'emd_id\'." ) if map3d_filename is not None or map3d_dataset is not None or map3d is not None or dx is not None: log_and_raise_error( logger, "Cannot initialize custom geometry because of ambiguous keyword arguments." ) sys.exit(1) self.set_custom_geometry_by_emd_id(emd_id) if diameter is None: self.diameter_mean = self._dx_orig * self._map3d_orig.shape[-1]