Example #1
0
 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
Example #2
0
 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
Example #3
0
 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
Example #4
0
 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
Example #5
0
 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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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