Exemplo n.º 1
0
 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"]
Exemplo n.º 2
0
 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"]
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
 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"]          
Exemplo n.º 6
0
 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")
Exemplo n.º 7
0
 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")
Exemplo n.º 8
0
 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
Exemplo n.º 9
0
 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
Exemplo n.º 10
0
 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.")
Exemplo n.º 11
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
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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]
Exemplo n.º 14
0
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."
            )
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
    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]
Exemplo n.º 17
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
Exemplo n.º 18
0
 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."
         )
Exemplo n.º 19
0
 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")
Exemplo n.º 20
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