def get_qmap(self, nx, ny, cx, cy, pixel_size, detector_distance, wavelength, extrinsic_rotation=None, order="xyz"): calculate = False if self._qmap_cache == {}: calculate = True else: calculate = calculate or nx != self._qmap_cache["nx"] calculate = calculate or ny != self._qmap_cache["ny"] calculate = calculate or cx != self._qmap_cache["cx"] calculate = calculate or cy != self._qmap_cache["cy"] calculate = calculate or pixel_size != self._qmap_cache["pixel_size"] calculate = calculate or detector_distance != self._qmap_cache["detector_distance"] calculate = calculate or wavelength != self._qmap_cache["wavelength"] calculate = calculate or order != self._qmap_cache["order"] if extrinsic_rotation is not None: calculate = calculate or not extrinsic_rotation.is_similar(self._qmap_cache["extrinsic_rotation"]) if calculate: log_debug(logger, "Calculating qmap") self._qmap_cache = { "qmap" : self.detector.generate_qmap(wavelength, cx=cx, cy=cy, extrinsic_rotation=extrinsic_rotation, order=order), "nx" : nx, "ny" : ny, "cx" : cx, "cy" : cy, "pixel_size" : pixel_size, "detector_distance" : detector_distance, "wavelength" : wavelength, "extrinsic_rotation": copy.deepcopy(extrinsic_rotation), "order" : order, } return self._qmap_cache["qmap"]
def get_qmap(self, nx, ny, cx, cy, pixel_size, detector_distance, wavelength, extrinsic_rotation=None, order="xyz"): calculate = False if self._qmap_cache == {}: calculate = True else: calculate = calculate or nx != self._qmap_cache["nx"] calculate = calculate or ny != self._qmap_cache["ny"] calculate = calculate or cx != self._qmap_cache["cx"] calculate = calculate or cy != self._qmap_cache["cy"] calculate = calculate or pixel_size != self._qmap_cache["pixel_size"] calculate = calculate or detector_distance != self._qmap_cache["detector_distance"] calculate = calculate or wavelength != self._qmap_cache["wavelength"] calculate = calculate or order != self._qmap_cache["order"] if extrinsic_rotation is not None: calculate = calculate or not extrinsic_rotation.is_similar(self._qmap_cache["extrinsic_rotation"]) if calculate: log_debug(logger, "Calculating qmap") self._qmap_cache = { "qmap" : self.detector.generate_qmap(wavelength, cx=cx, cy=cx, extrinsic_rotation=extrinsic_rotation, order=order), "nx" : nx, "ny" : ny, "cx" : cx, "cy" : cy, "pixel_size" : pixel_size, "detector_distance" : detector_distance, "wavelength" : wavelength, "extrinsic_rotation": copy.deepcopy(extrinsic_rotation), "order" : order, } return self._qmap_cache["qmap"]
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 get_qmap(self, nx, ny, cx, cy, pixel_size, detector_distance, wavelength, extrinsic_rotation=None, order="xyz"): calculate = False if self._qmap_cache == {}: calculate = True else: calculate = calculate or nx != self._qmap_cache["nx"] calculate = calculate or ny != self._qmap_cache["ny"] calculate = calculate or cx != self._qmap_cache["cx"] calculate = calculate or cy != self._qmap_cache["cy"] calculate = calculate or pixel_size != self._qmap_cache["pixel_size"] calculate = calculate or detector_distance != self._qmap_cache["detector_distance"] calculate = calculate or wavelength != self._qmap_cache["wavelength"] if extrinsic_rotation is not None: #print self._qmap_cache["extrinsic_rotation"] calculate = calculate or not extrinsic_rotation.is_similar(self._qmap_cache["extrinsic_rotation"]) if calculate: log_debug(logger, "Calculating qmap") Y,X = numpy.meshgrid(numpy.float64(numpy.arange(ny))-cy, numpy.float64(numpy.arange(nx))-cx, indexing="ij") self._qmap_cache = { "qmap" : condor.utils.scattering_vector.generate_qmap(X, Y, pixel_size, detector_distance, wavelength, extrinsic_rotation=extrinsic_rotation, order=order), "nx" : nx, "ny" : ny, "cx" : cx, "cy" : cy, "pixel_size" : pixel_size, "detector_distance" : detector_distance, "wavelength" : wavelength, "extrinsic_rotation": copy.deepcopy(extrinsic_rotation), } return self._qmap_cache["qmap"]
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__(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 _get_next_particles(self): D_particles = {} while len(D_particles) == 0: i = 0 for p in self.particles.values(): n = p.get_next_number_of_particles() for i_n in range(n): D_particles["particle_%02i" % i] = p.get_next() i += 1 N = len(D_particles) if N == 0: log_info(logger, "Miss - no particles in the interaction volume. Shooting again...") else: log_debug(logger, "%i particles" % N) return D_particles
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 _propagate(self, save_map3d=False, save_qmap=False, ndim=2, qn=None, qmax=None): if ndim not in [2,3]: log_and_raise_error(logger, "ndim = %i is an invalid input. Has to be either 2 or 3." % ndim) log_debug(logger, "Start propagation") # Iterate objects D_source = self.source.get_next() D_particles = self._get_next_particles() D_detector = self.detector.get_next() # Pull out variables nx = D_detector["nx"] ny = D_detector["ny"] cx = D_detector["cx"] cy = D_detector["cy"] pixel_size = D_detector["pixel_size"] detector_distance = D_detector["distance"] wavelength = D_source["wavelength"] # Qmap without rotation if ndim == 2: qmap0 = self.detector.generate_qmap(wavelength, cx=cx, cy=cy, extrinsic_rotation=None) else: qmax = numpy.sqrt((self.detector.get_q_max(wavelength, pos="edge")**2).sum()) qn = max([nx, ny]) qmap0 = self.detector.generate_qmap_3d(wavelength, qn=qn, qmax=qmax, extrinsic_rotation=None, order='xyz') if self.detector.solid_angle_correction: log_and_raise_error(logger, "Carrying out solid angle correction for a simulation of a 3D Fourier volume does not make sense. Please set solid_angle_correction=False for your Detector and try again.") return qmap_singles = {} F_tot = 0. # Calculate patterns of all single particles individually for particle_key, D_particle in D_particles.items(): p = D_particle["_class_instance"] # Intensity at interaction point pos = D_particle["position"] D_particle["intensity"] = self.source.get_intensity(pos, "ph/m2", pulse_energy=D_source["pulse_energy"]) I_0 = D_particle["intensity"] # Calculate primary wave amplitude # F0 = sqrt(I_0) 2pi/wavelength^2 F0 = numpy.sqrt(I_0)*2*numpy.pi/wavelength**2 D_particle["F0"] = F0 # 3D Orientation extrinsic_rotation = Rotation(values=D_particle["extrinsic_quaternion"], formalism="quaternion") if isinstance(p, condor.particle.ParticleSphere) or isinstance(p, condor.particle.ParticleSpheroid) or isinstance(p, condor.particle.ParticleMap): # Solid angles if self.detector.solid_angle_correction: Omega_p = self.detector.get_all_pixel_solid_angles(cx, cy) else: Omega_p = pixel_size**2 / detector_distance**2 # UNIFORM SPHERE if isinstance(p, condor.particle.ParticleSphere): # Refractive index dn = p.get_dn(wavelength) # Scattering vectors if ndim == 2: qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None) else: qmap = qmap0 q = numpy.sqrt((qmap**2).sum(axis=ndim)) # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*dn)**2 # Pattern F = condor.utils.sphere_diffraction.F_sphere_diffraction(K, q, R) * numpy.sqrt(Omega_p) # UNIFORM SPHEROID elif isinstance(p, condor.particle.ParticleSpheroid): if ndim == 3: log_and_raise_error(logger, "Spheroid simulation with ndim = 3 is not supported.") return # Refractive index dn = p.get_dn(wavelength) # Scattering vectors qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None, order="xyz") qx = qmap[:,:,0] qy = qmap[:,:,1] # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*abs(dn))**2 # Geometrical factors a = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_a(D_particle["diameter"], D_particle["flattening"]) c = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_c(D_particle["diameter"], D_particle["flattening"]) # Pattern # Spheroid axis before rotation v0 = numpy.array([0.,1.,0.]) v1 = extrinsic_rotation.rotate_vector(v0) theta = numpy.arcsin(v1[2]) phi = numpy.arctan2(-v1[0],v1[1]) F = condor.utils.spheroid_diffraction.F_spheroid_diffraction(K, qx, qy, a, c, theta, phi) * numpy.sqrt(Omega_p) # MAP elif isinstance(p, condor.particle.ParticleMap): # Resolution dx_required = self.detector.get_resolution_element_r(wavelength, cx=cx, cy=cy, center_variation=False) dx_suggested = self.detector.get_resolution_element_r(wavelength, center_variation=True) # Scattering vectors (the nfft requires order z,y,x) if ndim == 2: qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=extrinsic_rotation, order="zyx") else: qmap = self.detector.generate_qmap_3d(wavelength=wavelength, qn=qn, qmax=qmax, extrinsic_rotation=extrinsic_rotation, order="zyx") # Generate map map3d_dn, dx = p.get_new_dn_map(D_particle, dx_required, dx_suggested, wavelength) log_debug(logger, "Sampling of map: dx_required = %e m, dx_suggested = %e m, dx = %e m" % (dx_required, dx_suggested, dx)) if save_map3d: D_particle["map3d_dn"] = map3d_dn D_particle["dx"] = dx # Rescale and shape qmap for nfft qmap_scaled = dx * qmap / (2. * numpy.pi) qmap_shaped = qmap_scaled.reshape(int(qmap_scaled.size/3), 3) # Check inputs invalid_mask = ~((qmap_shaped>=-0.5) * (qmap_shaped<0.5)) if numpy.any(invalid_mask): qmap_shaped[invalid_mask] = 0. log_warning(logger, "%i invalid pixel positions." % invalid_mask.sum()) log_debug(logger, "Map3d input shape: (%i,%i,%i), number of dimensions: %i, sum %f" % (map3d_dn.shape[0], map3d_dn.shape[1], map3d_dn.shape[2], len(list(map3d_dn.shape)), abs(map3d_dn).sum())) if (numpy.isfinite(abs(map3d_dn))==False).sum() > 0: log_warning(logger, "There are infinite values in the dn map of the object.") log_debug(logger, "Scattering vectors shape: (%i,%i); Number of dimensions: %i" % (qmap_shaped.shape[0], qmap_shaped.shape[1], len(list(qmap_shaped.shape)))) if (numpy.isfinite(qmap_shaped)==False).sum() > 0: log_warning(logger, "There are infinite values in the scattering vectors.") # NFFT fourier_pattern = log_execution_time(logger)(condor.utils.nfft.nfft)(map3d_dn, qmap_shaped) # Check output - masking in case of invalid values if numpy.any(invalid_mask): fourier_pattern[invalid_mask.any(axis=1)] = numpy.nan # reshaping fourier_pattern = numpy.reshape(fourier_pattern, tuple(list(qmap_scaled.shape)[:-1])) log_debug(logger, "Generated pattern of shape %s." % str(fourier_pattern.shape)) F = F0 * fourier_pattern * dx**3 * numpy.sqrt(Omega_p) # ATOMS elif isinstance(p, condor.particle.ParticleAtoms): # Import here to make other functionalities of Condor independent of spsim import spsim # Check version from distutils.version import StrictVersion spsim_version_min = "0.1.0" if not hasattr(spsim, "__version__") or StrictVersion(spsim.__version__) < StrictVersion(spsim_version_min): log_and_raise_error(logger, "Your spsim version is too old. Please install the newest spsim version and try again.") sys.exit(0) # Create options struct opts = condor.utils.config._conf_to_spsim_opts(D_source, D_particle, D_detector, ndim=ndim, qn=qn, qmax=qmax) spsim.write_options_file("./spsim.confout",opts) # Create molecule struct mol = spsim.get_molecule_from_atoms(D_particle["atomic_numbers"], D_particle["atomic_positions"]) # Always recenter molecule spsim.origin_to_center_of_mass(mol) spsim.write_pdb_from_mol("./mol.pdbout", mol) # Calculate diffraction pattern pat = spsim.simulate_shot(mol, opts) # Extract complex Fourier values from spsim output F_img = spsim.make_cimage(pat.F, pat.rot, opts) phot_img = spsim.make_image(opts.detector.photons_per_pixel, pat.rot, opts) F = numpy.sqrt(abs(phot_img.image[:])) * numpy.exp(1.j * numpy.angle(F_img.image[:])) spsim.sp_image_free(F_img) spsim.sp_image_free(phot_img) # Extract qmap from spsim output if ndim == 2: qmap_img = spsim.sp_image_alloc(3, nx, ny) else: qmap_img = spsim.sp_image_alloc(3*qn, qn, qn) spsim.array_to_image(pat.HKL_list, qmap_img) if ndim == 2: qmap = 2*numpy.pi * qmap_img.image.real else: qmap = 2*numpy.pi * numpy.reshape(qmap_img.image.real, (qn, qn, qn, 3)) self._qmap_cache = { "qmap" : qmap, "nx" : nx, "ny" : ny, "cx" : cx, "cy" : cy, "pixel_size" : pixel_size, "detector_distance" : detector_distance, "wavelength" : wavelength, "extrinsic_rotation": copy.deepcopy(extrinsic_rotation), "order" : 'zyx', } spsim.sp_image_free(qmap_img) spsim.free_diffraction_pattern(pat) spsim.free_output_in_options(opts) else: log_and_raise_error(logger, "No valid particles initialized.") sys.exit(0) if save_qmap: qmap_singles[particle_key] = qmap v = D_particle["position"] # Calculate phase factors if needed if not numpy.allclose(v, numpy.zeros_like(v), atol=1E-12): if ndim == 2: F = F * numpy.exp(-1.j*(v[0]*qmap0[:,:,0]+v[1]*qmap0[:,:,1]+v[2]*qmap0[:,:,2])) else: F = F * numpy.exp(-1.j*(v[0]*qmap0[:,:,:,0]+v[1]*qmap0[:,:,:,1]+v[2]*qmap0[:,:,:,2])) # Superimpose patterns F_tot = F_tot + F # Polarization correction if ndim == 2: P = self.detector.calculate_polarization_factors(cx=cx, cy=cy, polarization=self.source.polarization) else: if self.source.polarization != "ignore": log_and_raise_error(logger, "polarization=\"%s\" for a 3D propagation does not make sense. Set polarization=\"ignore\" in your Source configuration and try again." % self.source.polarization) return P = 1. F_tot = numpy.sqrt(P) * F_tot # Photon detection I_tot, M_tot = self.detector.detect_photons(abs(F_tot)**2) if ndim == 2: M_tot_binary = M_tot == 0 if self.detector.binning is not None: IXxX_tot, MXxX_tot = self.detector.bin_photons(I_tot, M_tot) FXxX_tot, MXxX_tot = condor.utils.resample.downsample(F_tot, self.detector.binning, mode="integrate", mask2d0=M_tot, bad_bits=PixelMask.PIXEL_IS_IN_MASK, min_N_pixels=1) MXxX_tot_binary = None if MXxX_tot is None else (MXxX_tot == 0) else: M_tot_binary = None O = {} O["source"] = D_source O["particles"] = D_particles O["detector"] = D_detector O["entry_1"] = {} data_1 = {} data_1["data_fourier"] = F_tot data_1["data"] = I_tot data_1["mask"] = M_tot data_1["full_period_resolution"] = 2 * self.detector.get_max_resolution(wavelength) O["entry_1"]["data_1"] = data_1 if self.detector.binning is not None: data_2 = {} data_2["data_fourier"] = FXxX_tot data_2["data"] = IXxX_tot data_2["mask"] = MXxX_tot O["entry_1"]["data_2"] = data_2 O = remove_from_dict(O, "_") return O
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, 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]
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 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
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]
def _propagate(self, save_map3d=False, save_qmap=False, ndim=2, qn=None, qmax=None): if ndim not in [2,3]: log_and_raise_error(logger, "ndim = %i is an invalid input. Has to be either 2 or 3." % ndim) log_debug(logger, "Start propagation") # Iterate objects D_source = self.source.get_next() D_particles = self._get_next_particles() D_detector = self.detector.get_next() # Pull out variables nx = D_detector["nx"] ny = D_detector["ny"] cx = D_detector["cx"] cy = D_detector["cy"] pixel_size = D_detector["pixel_size"] detector_distance = D_detector["distance"] wavelength = D_source["wavelength"] # Qmap without rotation if ndim == 2: qmap0 = self.detector.generate_qmap(wavelength, cx=cx, cy=cy, extrinsic_rotation=None) else: qmax = numpy.sqrt((self.detector.get_q_max(wavelength, pos="edge")**2).sum()) qn = max([nx, ny]) qmap0 = self.detector.generate_qmap_3d(wavelength, qn=qn, qmax=qmax, extrinsic_rotation=None, order='xyz') if self.detector.solid_angle_correction: log_and_raise_error(logger, "Carrying out solid angle correction for a simulation of a 3D Fourier volume does not make sense. Please set solid_angle_correction=False for your Detector and try again.") return qmap_singles = {} F_tot = 0. # Calculate patterns of all single particles individually for particle_key, D_particle in D_particles.items(): p = D_particle["_class_instance"] # Intensity at interaction point pos = D_particle["position"] D_particle["intensity"] = self.source.get_intensity(pos, "ph/m2", pulse_energy=D_source["pulse_energy"]) I_0 = D_particle["intensity"] # Calculate primary wave amplitude # F0 = sqrt(I_0) 2pi/wavelength^2 F0 = numpy.sqrt(I_0)*2*numpy.pi/wavelength**2 D_particle["F0"] = F0 # 3D Orientation extrinsic_rotation = Rotation(values=D_particle["extrinsic_quaternion"], formalism="quaternion") if isinstance(p, condor.particle.ParticleSphere) or isinstance(p, condor.particle.ParticleSpheroid) or isinstance(p, condor.particle.ParticleMap): # Solid angles if self.detector.solid_angle_correction: Omega_p = self.detector.get_all_pixel_solid_angles(cx, cy) else: Omega_p = pixel_size**2 / detector_distance**2 # UNIFORM SPHERE if isinstance(p, condor.particle.ParticleSphere): # Refractive index dn = p.get_dn(wavelength) # Scattering vectors if ndim == 2: qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None) else: qmap = qmap0 q = numpy.sqrt((qmap**2).sum(axis=ndim)) # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*dn)**2 # Pattern F = condor.utils.sphere_diffraction.F_sphere_diffraction(K, q, R) * numpy.sqrt(Omega_p) # UNIFORM SPHEROID elif isinstance(p, condor.particle.ParticleSpheroid): if ndim == 3: log_and_raise_error(logger, "Spheroid simulation with ndim = 3 is not supported.") return # Refractive index dn = p.get_dn(wavelength) # Scattering vectors qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None, order="xyz") qx = qmap[:,:,0] qy = qmap[:,:,1] # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*abs(dn))**2 # Geometrical factors a = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_a(D_particle["diameter"], D_particle["flattening"]) c = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_c(D_particle["diameter"], D_particle["flattening"]) # Pattern # Spheroid axis before rotation v0 = numpy.array([0.,1.,0.]) v1 = extrinsic_rotation.rotate_vector(v0) theta = numpy.arcsin(v1[2]) phi = numpy.arctan2(-v1[0],v1[1]) F = condor.utils.spheroid_diffraction.F_spheroid_diffraction(K, qx, qy, a, c, theta, phi) * numpy.sqrt(Omega_p) # MAP elif isinstance(p, condor.particle.ParticleMap): # Resolution dx_required = self.detector.get_resolution_element_r(wavelength, cx=cx, cy=cy, center_variation=False) dx_suggested = self.detector.get_resolution_element_r(wavelength, center_variation=True) # Scattering vectors (the nfft requires order z,y,x) if ndim == 2: qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=extrinsic_rotation, order="zyx") else: qmap = self.detector.generate_qmap_3d(wavelength=wavelength, qn=qn, qmax=qmax, extrinsic_rotation=extrinsic_rotation, order="zyx") # Generate map map3d_dn, dx = p.get_new_dn_map(D_particle, dx_required, dx_suggested, wavelength) log_debug(logger, "Sampling of map: dx_required = %e m, dx_suggested = %e m, dx = %e m" % (dx_required, dx_suggested, dx)) if save_map3d: D_particle["map3d_dn"] = map3d_dn D_particle["dx"] = dx # Rescale and shape qmap for nfft qmap_scaled = dx * qmap / (2. * numpy.pi) qmap_shaped = qmap_scaled.reshape(qmap_scaled.size/3, 3) # Check inputs invalid_mask = ~((qmap_shaped>=-0.5) * (qmap_shaped<0.5)) if numpy.any(invalid_mask): qmap_shaped[invalid_mask] = 0. log_warning(logger, "%i invalid pixel positions." % invalid_mask.sum()) log_debug(logger, "Map3d input shape: (%i,%i,%i), number of dimensions: %i, sum %f" % (map3d_dn.shape[0], map3d_dn.shape[1], map3d_dn.shape[2], len(list(map3d_dn.shape)), abs(map3d_dn).sum())) if (numpy.isfinite(abs(map3d_dn))==False).sum() > 0: log_warning(logger, "There are infinite values in the dn map of the object.") log_debug(logger, "Scattering vectors shape: (%i,%i); Number of dimensions: %i" % (qmap_shaped.shape[0], qmap_shaped.shape[1], len(list(qmap_shaped.shape)))) if (numpy.isfinite(qmap_shaped)==False).sum() > 0: log_warning(logger, "There are infinite values in the scattering vectors.") # NFFT fourier_pattern = log_execution_time(logger)(condor.utils.nfft.nfft)(map3d_dn, qmap_shaped) # Check output - masking in case of invalid values if numpy.any(invalid_mask): fourier_pattern[invalid_mask.any(axis=1)] = numpy.nan # reshaping fourier_pattern = numpy.reshape(fourier_pattern, tuple(list(qmap_scaled.shape)[:-1])) log_debug(logger, "Generated pattern of shape %s." % str(fourier_pattern.shape)) F = F0 * fourier_pattern * dx**3 * numpy.sqrt(Omega_p) # ATOMS elif isinstance(p, condor.particle.ParticleAtoms): # Import here to make other functionalities of Condor independent of spsim import spsim # Check version from distutils.version import StrictVersion spsim_version_min = "0.1.0" if not hasattr(spsim, "__version__") or StrictVersion(spsim.__version__) < StrictVersion(spsim_version_min): log_and_raise_error(logger, "Your spsim version is too old. Please install the newest spsim version and try again.") sys.exit(0) # Create options struct opts = condor.utils.config._conf_to_spsim_opts(D_source, D_particle, D_detector, ndim=ndim, qn=qn, qmax=qmax) spsim.write_options_file("./spsim.confout",opts) # Create molecule struct mol = spsim.get_molecule_from_atoms(D_particle["atomic_numbers"], D_particle["atomic_positions"]) # Always recenter molecule spsim.origin_to_center_of_mass(mol) spsim.write_pdb_from_mol("./mol.pdbout", mol) # Calculate diffraction pattern pat = spsim.simulate_shot(mol, opts) # Extract complex Fourier values from spsim output F_img = spsim.make_cimage(pat.F, pat.rot, opts) phot_img = spsim.make_image(opts.detector.photons_per_pixel, pat.rot, opts) F = numpy.sqrt(abs(phot_img.image[:])) * numpy.exp(1.j * numpy.angle(F_img.image[:])) spsim.sp_image_free(F_img) spsim.sp_image_free(phot_img) # Extract qmap from spsim output if ndim == 2: qmap_img = spsim.sp_image_alloc(3, nx, ny) else: qmap_img = spsim.sp_image_alloc(3*qn, qn, qn) spsim.array_to_image(pat.HKL_list, qmap_img) if ndim == 2: qmap = 2*numpy.pi * qmap_img.image.real else: qmap = 2*numpy.pi * numpy.reshape(qmap_img.image.real, (qn, qn, qn, 3)) spsim.sp_image_free(qmap_img) spsim.free_diffraction_pattern(pat) spsim.free_output_in_options(opts) else: log_and_raise_error(logger, "No valid particles initialized.") sys.exit(0) if save_qmap: qmap_singles[particle_key] = qmap v = D_particle["position"] # Calculate phase factors if needed if not numpy.allclose(v, numpy.zeros_like(v), atol=1E-12): if ndim == 2: F = F * numpy.exp(-1.j*(v[0]*qmap0[:,:,0]+v[1]*qmap0[:,:,1]+v[2]*qmap0[:,:,2])) else: F = F * numpy.exp(-1.j*(v[0]*qmap0[:,:,:,0]+v[1]*qmap0[:,:,:,1]+v[2]*qmap0[:,:,:,2])) # Superimpose patterns F_tot = F_tot + F # Polarization correction if ndim == 2: P = self.detector.calculate_polarization_factors(cx=cx, cy=cy, polarization=self.source.polarization) else: if self.source.polarization != "ignore": log_and_raise_error(logger, "polarization=\"%s\" for a 3D propagation does not make sense. Set polarization=\"ignore\" in your Source configuration and try again." % self.source.polarization) return P = 1. F_tot = numpy.sqrt(P) * F_tot # Photon detection I_tot, M_tot = self.detector.detect_photons(abs(F_tot)**2) if ndim == 2: M_tot_binary = M_tot == 0 if self.detector.binning is not None: IXxX_tot, MXxX_tot = self.detector.bin_photons(I_tot, M_tot) FXxX_tot, MXxX_tot = condor.utils.resample.downsample(F_tot, self.detector.binning, mode="integrate", mask2d0=M_tot, bad_bits=PixelMask.PIXEL_IS_IN_MASK, min_N_pixels=1) MXxX_tot_binary = None if MXxX_tot is None else (MXxX_tot == 0) else: M_tot_binary = None O = {} O["source"] = D_source O["particles"] = D_particles O["detector"] = D_detector O["entry_1"] = {} data_1 = {} data_1["data_fourier"] = F_tot data_1["data"] = I_tot data_1["mask"] = M_tot data_1["full_period_resolution"] = 2 * self.detector.get_max_resolution(wavelength) O["entry_1"]["data_1"] = data_1 if self.detector.binning is not None: data_2 = {} data_2["data_fourier"] = FXxX_tot data_2["data"] = IXxX_tot data_2["mask"] = MXxX_tot O["entry_1"]["data_2"] = data_2 O = remove_from_dict(O, "_") return O
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 __init__(self, wavelength, focus_diameter, pulse_energy, profile_model=None, pulse_energy_variation=None, pulse_energy_spread=None, pulse_energy_variation_n=None): 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) log_debug(logger, "Source configured")
def propagate(self, save_map3d = False, save_qmap = False): log_debug(logger, "Start propagation") # Iterate objects D_source = self.source.get_next() D_particles = self._get_next_particles() D_detector = self.detector.get_next() # Pull out variables nx = D_detector["nx"] ny = D_detector["ny"] cx = D_detector["cx"] cy = D_detector["cy"] pixel_size = D_detector["pixel_size"] detector_distance = D_detector["distance"] wavelength = D_source["wavelength"] # Qmap without rotation Y,X = numpy.meshgrid(numpy.float64(numpy.arange(ny))-cy, numpy.float64(numpy.arange(nx))-cx, indexing="ij") qmap0 = condor.utils.scattering_vector.generate_qmap(X, Y, pixel_size, detector_distance, wavelength, extrinsic_rotation=None) qmap_singles = {} F_tot = None # Calculate patterns of all single particles individually for particle_key, D_particle in D_particles.items(): p = D_particle["_class_instance"] # Intensity at interaction point pos = D_particle["position"] D_particle["intensity"] = self.source.get_intensity(pos, "ph/m2", pulse_energy=D_source["pulse_energy"]) I_0 = D_particle["intensity"] # Calculate primary wave amplitude # F0 = sqrt(I_0) 2pi/wavelength^2 F0 = numpy.sqrt(I_0)*2*numpy.pi/wavelength**2 D_particle["F0"] = F0 # 3D Orientation extrinsic_rotation = Rotation(values=D_particle["extrinsic_quaternion"], formalism="quaternion") # Resolution dx_required = self.detector.get_resolution_element_r(wavelength, cx=cx, cy=cy, center_variation=False) dx_suggested = self.detector.get_resolution_element_r(wavelength, center_variation=True) # UNIFORM SPHERE if isinstance(p, condor.particle.ParticleSphere): # Refractive index dn = p.get_dn(wavelength) # Scattering vectors qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None) q = numpy.sqrt(qmap[:,:,0]**2+qmap[:,:,1]**2) # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*dn)**2 # Geometrical factor Omega_p = self.detector.get_all_pixel_solid_angles(cx, cy) # Pattern F = condor.utils.sphere_diffraction.F_sphere_diffraction(K, q, R) * numpy.sqrt(Omega_p) # UNIFORM SPHEROID elif isinstance(p, condor.particle.ParticleSpheroid): # Refractive index dn = p.get_dn(wavelength) # Scattering vectors qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=None, order="xyz") qx = qmap[:,:,0] qy = qmap[:,:,1] # Intensity scaling factor R = D_particle["diameter"]/2. V = 4/3.*numpy.pi*R**3 K = (F0*V*abs(dn))**2 # Geometrical factors a = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_a(D_particle["diameter"], D_particle["flattening"]) c = condor.utils.spheroid_diffraction.to_spheroid_semi_diameter_c(D_particle["diameter"], D_particle["flattening"]) Omega_p = self.detector.get_all_pixel_solid_angles(cx, cy) # Pattern # Spheroid axis before rotation v0 = numpy.array([0.,1.,0.]) v1 = extrinsic_rotation.rotate_vector(v0) theta = numpy.arcsin(v1[2]) phi = numpy.arctan2(-v1[0],v1[1]) F = condor.utils.spheroid_diffraction.F_spheroid_diffraction(K, qx, qy, a, c, theta, phi) * numpy.sqrt(Omega_p) # MAP elif isinstance(p, condor.particle.ParticleMap): # Scattering vectors (the nfft requires order z,y,x) qmap = self.get_qmap(nx=nx, ny=ny, cx=cx, cy=cy, pixel_size=pixel_size, detector_distance=detector_distance, wavelength=wavelength, extrinsic_rotation=extrinsic_rotation, order="zyx") # Geometrical factor Omega_p = self.detector.get_all_pixel_solid_angles(cx, cy) # Generate map map3d_dn, dx = p.get_new_dn_map(D_particle, dx_required, dx_suggested, wavelength) log_debug(logger, "Sampling of map: dx_required = %e m, dx_suggested = %e m, dx = %e m" % (dx_required, dx_suggested, dx)) if save_map3d: D_particle["map3d_dn"] = map3d_dn D_particle["dx"] = dx # Rescale and shape qmap for nfft qmap_scaled = dx * qmap / (2. * numpy.pi) qmap_shaped = qmap_scaled.reshape(qmap_scaled.shape[0]*qmap_scaled.shape[1], 3) # For Jing: #D_particle["qmap3d"] = detector.generate_qmap_ori(nfft_scaled=True) # Check inputs invalid_mask = ((qmap_shaped>=-0.5) * (qmap_shaped<0.5)) == False if (invalid_mask).sum() > 0: qmap_shaped[invalid_mask] = 0. log_warning(logger, "%i invalid pixel positions." % invalid_mask.sum()) log_debug(logger, "Map3d input shape: (%i,%i,%i), number of dimensions: %i, sum %f" % (map3d_dn.shape[0], map3d_dn.shape[1], map3d_dn.shape[2], len(list(map3d_dn.shape)), abs(map3d_dn).sum())) if (numpy.isfinite(abs(map3d_dn))==False).sum() > 0: log_warning(logger, "There are infinite values in the dn map of the object.") log_debug(logger, "Scattering vectors shape: (%i,%i); Number of dimensions: %i" % (qmap_shaped.shape[0], qmap_shaped.shape[1], len(list(qmap_shaped.shape)))) if (numpy.isfinite(qmap_shaped)==False).sum() > 0: log_warning(logger, "There are infinite values in the scattering vectors.") # NFFT fourier_pattern = log_execution_time(logger)(condor.utils.nfft.nfft)(map3d_dn, qmap_shaped) # Check output - masking in case of invalid values if (invalid_mask).sum() > 0: fourier_pattern[numpy.any(invalid_mask)] = numpy.nan # reshaping fourier_pattern = numpy.reshape(fourier_pattern, (qmap_scaled.shape[0], qmap_scaled.shape[1])) log_debug(logger, "Got pattern of %i x %i pixels." % (fourier_pattern.shape[1], fourier_pattern.shape[0])) #F = F0 * fourier_pattern * dx_required**3 * numpy.sqrt(Omega_p) F = F0 * fourier_pattern * dx**3 * numpy.sqrt(Omega_p) # ATOMS elif isinstance(p, condor.particle.ParticleAtoms): # Import only here (otherwise errors if spsim library not installed) import spsim # Create options struct opts = condor.utils.config._conf_to_spsim_opts(D_source, D_particle, D_detector) spsim.write_options_file("./spsim.confout",opts) # Create molecule struct mol = spsim.get_molecule_from_atoms(D_particle["atomic_numbers"], D_particle["atomic_positions"]) # Always recenter molecule spsim.origin_to_center_of_mass(mol) spsim.write_pdb_from_mol("./mol.pdbout", mol) # Calculate diffraction pattern pat = spsim.simulate_shot(mol, opts) # Extract complex Fourier values from spsim output F_img = spsim.make_cimage(pat.F, pat.rot, opts) phot_img = spsim.make_image(opts.detector.photons_per_pixel, pat.rot, opts) F = numpy.sqrt(abs(phot_img.image[:])) * numpy.exp(1.j * numpy.angle(F_img.image[:])) spsim.sp_image_free(F_img) spsim.sp_image_free(phot_img) # Extract qmap from spsim output qmap_img = spsim.sp_image_alloc(3,D_detector["nx"], D_detector["ny"]) spsim.array_to_image(pat.HKL_list, qmap_img) qmap = numpy.zeros(shape=(D_detector["ny"], D_detector["nx"], 3)) qmap[:,:,0] = 2*numpy.pi*qmap_img.image.real[:,:,0] qmap[:,:,1] = 2*numpy.pi*qmap_img.image.real[:,:,1] qmap[:,:,2] = 2*numpy.pi*qmap_img.image.real[:,:,2] spsim.sp_image_free(qmap_img) spsim.free_diffraction_pattern(pat) spsim.free_output_in_options(opts) else: log_and_raise_error(logger, "No valid particles initialized.") sys.exit(0) if save_qmap: qmap_singles[particle_key] = qmap # Superimpose patterns if F_tot is None: F_tot = numpy.zeros_like(F) v = D_particle["position"] F_tot = F_tot + F * numpy.exp(-1.j*(v[0]*qmap0[:,:,0]+v[1]*qmap0[:,:,1]+v[2]*qmap0[:,:,2])) I_tot, M_tot = self.detector.detect_photons(abs(F_tot)**2) IXxX_tot, MXxX_tot = self.detector.bin_photons(I_tot, M_tot) if self.detector.binning is not None: FXxX_tot, MXxX_tot = condor.utils.resample.downsample(F_tot, self.detector.binning, mode="integrate", mask2d0=M_tot, bad_bits=PixelMask.PIXEL_IS_IN_MASK, min_N_pixels=1) M_tot_binary = M_tot == 0 MXxX_tot_binary = None if MXxX_tot is None else (MXxX_tot == 0) O = {} O["source"] = D_source O["particles"] = D_particles O["detector"] = D_detector O["entry_1"] = {} data_1 = {} data_1["data_fourier"] = F_tot data_1["data"] = I_tot data_1["mask"] = M_tot data_1["full_period_resolution"] = 2 * self.detector.get_max_resolution(wavelength) O["entry_1"]["data_1"] = data_1 if self.detector.binning is not None: data_2 = {} data_2["data_fourier"] = FXxX_tot data_2["data"] = IXxX_tot data_2["mask"] = MXxX_tot O["entry_1"]["data_2"] = data_2 O = remove_from_dict(O, "_") return O