Exemplo n.º 1
0
def test_map_interpolation(tolerance=0.1):
    import logging
    logger = logging.getLogger("condor")
    logger.setLevel("DEBUG")
    src = condor.Source(wavelength=10.0E-9,
                        pulse_energy=1E-3,
                        focus_diameter=1E-6)
    det = condor.Detector(distance=1.0, pixel_size=300E-6, nx=256, ny=256)
    par = condor.ParticleMap(diameter=600E-9,
                             material_type="cell",
                             geometry="custom",
                             map3d_filename=here + "/examples/map3d.h5",
                             map3d_dataset="data",
                             dx=5E-9)
    s = "particle_map"
    E = condor.Experiment(src, {s: par}, det)
    res0 = E.propagate()
    I0 = res0["entry_1"]["data_1"]["data"]
    # Now without interpolation
    print "NOW WITHOUT INTERPOLATION"
    condor.particle.particle_map.ENABLE_MAP_INTERPOLATION = False
    src = condor.Source(wavelength=10.0E-9,
                        pulse_energy=1E-3,
                        focus_diameter=1E-6)
    det = condor.Detector(distance=1.0, pixel_size=300E-6, nx=256, ny=256)
    par = condor.ParticleMap(diameter=600E-9,
                             material_type="cell",
                             geometry="custom",
                             map3d_filename=here + "/examples/map3d.h5",
                             map3d_dataset="data",
                             dx=5E-9)
    s = "particle_map"
    E = condor.Experiment(src, {s: par}, det)
    res1 = E.propagate()
    I1 = res1["entry_1"]["data_1"]["data"]
    import matplotlib.pyplot as pypl
    pypl.imsave("./Imap_interp.png", abs(I0), vmin=0, vmax=I0.max())
    pypl.imsave("./Imap_no_interp.png", abs(I1), vmin=0, vmax=I0.max())
    err = abs(I0 - I1).sum() / ((I0 + I1).sum() / 2.)
    if err < tolerance:
        print "\t=> Test successful (err = %e)" % err
        return False
    else:
        print "\t=> Test failed (err = %e)" % err
        return True
Exemplo n.º 2
0
import numpy
import condor

# Construct Source instance
src = condor.Source(wavelength=0.1E-9, pulse_energy=1E-3, focus_diameter=1E-6)

# Construct Detector instance
det = condor.Detector(distance=0.05, pixel_size=110E-6, nx=1000, ny=1000)

# Construct ParticleSphere instance
par = condor.ParticleSphere(diameter=1E-9, material_type="water")

# Combine source, detector, and particle by constructing Experiment instance
E = condor.Experiment(src, {"particle_sphere" : par}, det)

# Calculate diffraction pattern and obtain results in a dict
res = E.propagate()

# Arrays for Fourier and real space
data_fourier = res["entry_1"]["data_1"]["data_fourier"]
real_space = numpy.fft.fftshift(numpy.fft.ifftn(data_fourier))
Exemplo n.º 3
0
else:
    N = 2
    rotation_formalism = "random"
    rotation_values = None

focus_diameter = 100e-9
intensity = 2 * 1.02646137e9
pulse_energy = ((focus_diameter**2) * numpy.pi / 4.) * intensity
wavelength = 0.2262e-9
pixelsize = 110e-6
distance = 2.4
dx = 3.76e-10

# Source
src = condor.Source(wavelength=wavelength,
                    pulse_energy=pulse_energy,
                    focus_diameter=focus_diameter)
# Detector
det = condor.Detector(distance=distance, pixel_size=pixelsize, nx=414, ny=414)
# Map
print "Simulating map"
par = condor.ParticleMap(diameter=40E-9,
                         material_type="poliovirus",
                         geometry="custom",
                         map3d_filename="../../map3d.h5",
                         map3d_dataset="data",
                         dx=dx,
                         rotation_formalism=rotation_formalism,
                         rotation_values=rotation_values)
s = "particle_map"
E = condor.Experiment(src, {s: par}, det)
Exemplo n.º 4
0
    def calculate(self, structure, **kwargs):
        """Calculate the descriptor for the given ASE structure.

        Parameters:

        structure: `ase.Atoms` object
            Atomic structure.

        """

        atoms = scale_structure(
            structure,
            scaling_type=self.atoms_scaling,
            atoms_scaling_cutoffs=self.atoms_scaling_cutoffs)

        # Source
        src = condor.Source(**self.param_source)

        # Detector
        det = condor.Detector(**self.param_detector)

        # Atoms
        atomic_numbers = map(lambda el: el.number, atoms)
        atomic_numbers = [
            atomic_number + 2 for atomic_number in atomic_numbers
        ]

        # convert Angstrom to m (CONDOR uses meters)
        atomic_positions = map(
            lambda pos: [pos.x * 1E-10, pos.y * 1E-10, pos.z * 1E-10], atoms)

        intensity_rgb = []
        rs_rgb = []
        ph_rgb = []
        real_space = None
        phases = None

        for [_, rot_matrices] in iteritems(self.rot_matrices):
            # loop over channels
            intensity_channel = []
            rs_channel = []
            ph_channel = []
            for rot_matrix in rot_matrices:
                # loop over the rotation matrices in a given channel
                # and sum the intensities in the same channel
                quaternion = condor.utils.rotation.quat_from_rotmx(rot_matrix)
                rotation_values = np.array([quaternion])
                rotation_formalism = 'quaternion'
                rotation_mode = 'intrinsic'

                par = condor.ParticleAtoms(
                    atomic_numbers=atomic_numbers,
                    atomic_positions=atomic_positions,
                    rotation_values=rotation_values,
                    rotation_formalism=rotation_formalism,
                    rotation_mode=rotation_mode)

                s = 'particle_atoms'
                condor_exp = condor.Experiment(src, {s: par}, det)
                res = condor_exp.propagate()

                # retrieve results
                real_space = np.fft.fftshift(
                    np.fft.ifftn(
                        np.fft.fftshift(
                            res["entry_1"]["data_1"]["data_fourier"])))
                intensity = res["entry_1"]["data_1"]["data"]
                fourier_space = res["entry_1"]["data_1"]["data_fourier"]
                phases = np.angle(fourier_space) % (2 * np.pi)

                if self.use_mask:
                    # set to zero values outside a ring-like mask
                    xc = (self.n_px - 1.0) / 2.0
                    yc = (self.n_py - 1.0) / 2.0
                    n = self.n_px
                    a, b = xc, yc
                    x, y = np.ogrid[-a:n - a, -b:n - b]

                    mask_int = x * x + y * y <= self.mask_r_min * self.mask_r_min
                    mask_ext = x * x + y * y >= self.mask_r_max * self.mask_r_max

                    for i in range(self.n_px):
                        for j in range(self.n_py):
                            if mask_int[i, j]:
                                intensity[i, j] = 0.0
                            if mask_ext[i, j]:
                                intensity[i, j] = 0.0

                intensity_channel.append(intensity)
                rs_channel.append(real_space.real)
                ph_channel.append(phases)

            # first sum the angles within the channel, then normalize
            intensity_channel = np.asarray(intensity_channel).sum(axis=0)
            rs_channel = np.asarray(rs_channel).sum(axis=0)
            ph_channel = np.asarray(ph_channel).sum(axis=0)

            # append normalized data from different channels
            # and divide by the angles per channel
            intensity_rgb.append(intensity_channel)
            rs_rgb.append(rs_channel)
            ph_rgb.append(ph_channel)

        intensity_rgb = np.asarray(intensity_rgb)
        intensity_rgb = (intensity_rgb - intensity_rgb.min()) / (
            intensity_rgb.max() - intensity_rgb.min())

        rs8 = (((real_space.real - real_space.real.min()) /
                (real_space.real.max() - real_space.real.min())) *
               255.0).astype(np.uint8)
        ph8 = (((phases - phases.min()) / (phases.max() - phases.min())) *
               255.0).astype(np.uint8)

        # reshape to have nb of color channels last
        intensity_rgb = intensity_rgb.reshape(intensity_rgb.shape[1],
                                              intensity_rgb.shape[2],
                                              intensity_rgb.shape[0])

        # add results in ASE structure info
        descriptor_data = dict(descriptor_name=self.name,
                               descriptor_info=str(self),
                               diffraction_2d_intensity=intensity_rgb,
                               diffraction_2d_real_space=rs8,
                               diffraction_2d_phase=ph8)

        structure.info['descriptor'] = descriptor_data

        return structure
Exemplo n.º 5
0
import numpy
import matplotlib.pyplot as pypl

import condor

# Construct source, sample, detector instanec
src = condor.Source(wavelength=0.1E-9,
                    pulse_energy=1E-3,
                    pulse_energy_variation="normal",
                    pulse_energy_spread=1E-4,
                    focus_diameter=1E-6)
det = condor.Detector(distance=0.05, pixel_size=110E-6, nx=1000, ny=1000)

# Construct particle instance
par = condor.ParticleSphere(diameter=1E-9, material_type="water")

# Construct experiment instance
E = condor.Experiment(src, {"particle_sphere": par}, det)

# Calculate diffraction pattern
res = E.propagate()

# Arrays for Fourier and real space
data_fourier = res["entry_1"]["data_1"]["data_fourier"]
real_space = numpy.fft.fftshift(numpy.fft.ifftn(data_fourier))
Exemplo n.º 6
0
this_dir = os.path.dirname(os.path.realpath(__file__))

import condor

import logging
logger = logging.getLogger("condor")
#logger.setLevel("DEBUG")
logger.setLevel("WARNING")
#logger.setLevel("INFO")

N = 1
rotation_formalism="random"
rotation_values = None

# Source
src = condor.Source(wavelength=1E-10, pulse_energy=1E-3, focus_diameter=1001E-9)
# Detector
det = condor.Detector(distance=0.2, pixel_size=800E-6, nx=250, ny=250)
# Map
#print("Simulating map")
par = condor.ParticleAtoms(pdb_filename="%s/../../DNA.pdb" % this_dir,
                           rotation_formalism=rotation_formalism, rotation_values=rotation_values)
s = "particle_atoms"
E = condor.Experiment(src, {s : par}, det)

W = condor.utils.cxiwriter.CXIWriter("./condor.cxi")
for i in range(N):
    t = time.time()
    res = E.propagate()
    #print(time.time()-t)
    if plotting:
Exemplo n.º 7
0
def test_compare_atoms_with_map(tolerance=0.1):
    """
    Compare the output of two diffraction patterns, one simulated with descrete atoms (spsim) and the other one from a 3D refractive index map on a regular grid.
    """
    src = condor.Source(wavelength=0.1E-9,
                        pulse_energy=1E-3,
                        focus_diameter=1E-6)
    det = condor.Detector(distance=0.5,
                          pixel_size=750E-6,
                          nx=100,
                          ny=100,
                          cx=45,
                          cy=59)
    angle_d = 72.
    angle = angle_d / 360. * 2 * numpy.pi
    rotation_axis = numpy.array([0.43, 0.643, 0.])
    rotation_axis = rotation_axis / condor.utils.linalg.length(rotation_axis)
    quaternion = condor.utils.rotation.quat(angle, rotation_axis[0],
                                            rotation_axis[1], rotation_axis[2])
    rotation_values = numpy.array([quaternion])
    rotation_formalism = "quaternion"
    rotation_mode = "extrinsic"
    short_diameter = 25E-9 * 12 / 100.
    long_diameter = 2 * short_diameter
    N_long = 20
    N_short = int(round(short_diameter / long_diameter * N_long))
    dx = long_diameter / (N_long - 1)
    massdensity = condor.utils.material.get_atomic_mass(
        "H") * scipy.constants.value("atomic mass constant") / dx**3
    # Map
    map3d = numpy.zeros(shape=(N_long, N_long, N_long))
    map3d[:N_short, :, :N_short] = 1.
    map3d[N_short:N_short + N_short, :N_short, :N_short] = 1.
    par = condor.ParticleMap(diameter=long_diameter,
                             material_type="custom",
                             massdensity=massdensity,
                             atomic_composition={"H": 1.},
                             geometry="custom",
                             map3d=map3d,
                             dx=dx,
                             rotation_values=rotation_values,
                             rotation_formalism=rotation_formalism,
                             rotation_mode=rotation_mode)
    s = "particle_map_custom"
    E = condor.Experiment(src, {s: par}, det)
    res = E.propagate()
    F_map = res["entry_1"]["data_1"]["data_fourier"]
    # Atoms
    Z1, Y1, X1 = numpy.meshgrid(numpy.linspace(0, short_diameter, N_short),
                                numpy.linspace(0, long_diameter, N_long),
                                numpy.linspace(0, short_diameter, N_short),
                                indexing="ij")
    Z2, Y2, X2 = numpy.meshgrid(numpy.linspace(0, short_diameter, N_short) +
                                long_diameter / 2.,
                                numpy.linspace(0, short_diameter, N_short),
                                numpy.linspace(0, short_diameter, N_short),
                                indexing="ij")
    Z = numpy.concatenate((Z1.ravel(), Z2.ravel()))
    Y = numpy.concatenate((Y1.ravel(), Y2.ravel()))
    X = numpy.concatenate((X1.ravel(), X2.ravel()))
    atomic_positions = numpy.array(
        [[x, y, z] for x, y, z in zip(X.ravel(), Y.ravel(), Z.ravel())])
    atomic_numbers = numpy.ones(atomic_positions.size // 3, dtype=numpy.int16)
    par = condor.ParticleAtoms(atomic_positions=atomic_positions,
                               atomic_numbers=atomic_numbers,
                               rotation_values=rotation_values,
                               rotation_formalism=rotation_formalism,
                               rotation_mode=rotation_mode)
    s = "particle_atoms"
    E = condor.Experiment(src, {s: par}, det)
    res = E.propagate()
    F_atoms = res["entry_1"]["data_1"]["data_fourier"]
    # Compare
    I_atoms = abs(F_atoms)**2
    I_map = abs(F_map)**2
    diff = I_atoms - I_map
    err = abs(diff).sum() / ((I_atoms.sum() + I_map.sum()) / 2.)
    if SAVE_OUTPUT:
        import matplotlib.pyplot as pypl
        pypl.imsave("./Iatoms_mol.png", abs(I_atoms))
        pypl.imsave("./Iatoms_map.png", abs(I_map))
    assert err < tolerance
Exemplo n.º 8
0
def test_compare_spheroid_with_map(tolerance=0.15):
    """
    Compare the output of two diffraction patterns, one simulated with the direct formula and the other one from a 3D refractive index map on a regular grid
    """
    src = condor.Source(wavelength=0.1E-9,
                        pulse_energy=1E-3,
                        focus_diameter=1E-6)
    det = condor.Detector(distance=0.5,
                          pixel_size=750E-6,
                          nx=100,
                          ny=100,
                          cx=45,
                          cy=59)
    angle_d = 72.
    angle = angle_d / 360. * 2 * numpy.pi
    rotation_axis = numpy.array([0.43, 0.643, 0.])
    rotation_axis = rotation_axis / condor.utils.linalg.length(rotation_axis)
    quaternion = condor.utils.rotation.quat(angle, rotation_axis[0],
                                            rotation_axis[1], rotation_axis[2])
    rotation_values = numpy.array([quaternion])
    rotation_formalism = "quaternion"
    rotation_mode = "extrinsic"
    short_diameter = 25E-9 * 12 / 100.
    long_diameter = 2 * short_diameter
    spheroid_diameter = condor.utils.spheroid_diffraction.to_spheroid_diameter(
        short_diameter / 2., long_diameter / 2.)
    spheroid_flattening = condor.utils.spheroid_diffraction.to_spheroid_flattening(
        short_diameter / 2., long_diameter / 2.)
    # Ideal spheroid
    par = condor.ParticleSpheroid(diameter=spheroid_diameter,
                                  material_type="water",
                                  flattening=spheroid_flattening,
                                  rotation_values=rotation_values,
                                  rotation_formalism=rotation_formalism,
                                  rotation_mode=rotation_mode)
    s = "particle_spheroid"
    E = condor.Experiment(src, {s: par}, det)
    res = E.propagate()
    F_ideal = res["entry_1"]["data_1"]["data_fourier"]
    # Map (spheroid)
    par = condor.ParticleMap(diameter=spheroid_diameter,
                             material_type="water",
                             flattening=spheroid_flattening,
                             geometry="spheroid",
                             rotation_values=rotation_values,
                             rotation_formalism=rotation_formalism,
                             rotation_mode=rotation_mode)
    s = "particle_map_spheroid"
    E = condor.Experiment(src, {s: par}, det)
    res = E.propagate()
    F_map = res["entry_1"]["data_1"]["data_fourier"]
    # Compare
    I_ideal = abs(F_ideal)**2
    I_map = abs(F_map)**2
    if SAVE_OUTPUT:
        import matplotlib.pyplot as pypl
        pypl.imsave("./Ispheroid_sph.png", abs(I_ideal))
        pypl.imsave("./Ispheroid_map.png", abs(I_map))
    diff = I_ideal - I_map
    err = abs(diff).sum() / ((I_ideal.sum() + I_map.sum()) / 2.)
    assert err < tolerance
Exemplo n.º 9
0
import numpy as np
import scipy.interpolate
import condor

# Number of frames
N = 100

# Dimensions in diffraction space
nx, ny, nz = (100, 100, 100)

S = condor.Source(wavelength=1E-9, focus_diameter=1E-6, pulse_energy=1E-3)
P = condor.ParticleMap(geometry="icosahedron",
                       diameter=100E-9,
                       material_type="cell",
                       rotation_formalism="random")
D = condor.Detector(pixel_size=1000E-6, distance=0.5, nx=nx, ny=ny)

E = condor.Experiment(source=S, particles={"particle_map": P}, detector=D)

points = []
values = []
for i in range(N):
    res = E.propagate()
    img = res["entry_1"]["data_1"]["data_fourier"]
    qmap = E.get_qmap_from_cache()
    c = 2 * np.pi * D.pixel_size / (S.photon.get_wavelength() * D.distance)
    points.append(qmap.reshape((qmap.shape[0] * qmap.shape[1], 3)) / c)
    values.append(img.flatten())
points = np.array(points)
points = points.reshape((points.shape[0] * points.shape[1], 3))
values = np.array(values).flatten()
Exemplo n.º 10
0
    def calculate(self,
                  structure,
                  min_nb_atoms=20,
                  plot_3d=False,
                  plot_slices=False,
                  plot_slices_sph_coords=False,
                  save_diff_intensity=True,
                  **kwargs):
        """Calculate the descriptor for the given ASE structure.

        Parameters:

        structure: `ase.Atoms` object
            Atomic structure.

        min_nb_atoms: int, optional (default=20)
            If the structure contains less than ``min_nb_atoms``, the descriptor is not calculated and an array with
            zeros is return as descriptor. This is because the descriptor is expected to be no longer meaningful for
            such a small amount of atoms present in the chosen structure.

        """

        if len(structure) > min_nb_atoms - 1:

            atoms = scale_structure(
                structure,
                scaling_type=self.atoms_scaling,
                atoms_scaling_cutoffs=self.atoms_scaling_cutoffs,
                extrinsic_scale_factor=self.extrinsic_scale_factor)

            # Source
            src = condor.Source(**self.param_source)

            # Detector
            # solid_angle_correction are meaningless for 3d diffraction
            det = condor.Detector(solid_angle_correction=False,
                                  **self.param_detector)

            # Atoms
            atomic_numbers = map(lambda el: el.number, atoms)
            atomic_numbers = [
                atomic_number + 5 for atomic_number in atomic_numbers
            ]
            # atomic_numbers = [82 for atomic_number in atomic_numbers]

            # convert Angstrom to m (CONDOR uses meters)
            atomic_positions = map(
                lambda pos: [pos.x * 1E-10, pos.y * 1E-10, pos.z * 1E-10],
                atoms)

            par = condor.ParticleAtoms(atomic_numbers=atomic_numbers,
                                       atomic_positions=atomic_positions)

            s = "particle_atoms"
            condor_exp = condor.Experiment(src, {s: par}, det)
            res = condor_exp.propagate3d()

            # retrieve some physical quantities that might be useful for users
            intensity = res["entry_1"]["data_1"]["data"]
            fourier_space = res["entry_1"]["data_1"]["data_fourier"]
            phases = np.angle(fourier_space) % (2 * np.pi)

            # 3D diffraction calculation
            real_space = np.fft.fftshift(
                np.fft.ifftn(
                    np.fft.fftshift(res["entry_1"]["data_1"]["data_fourier"])))
            window = get_window(self.window, self.n_px)
            tot_density = window * real_space.real
            center_of_mass = ndimage.measurements.center_of_mass(tot_density)
            logger.debug("Tot density data dimensions: {}".format(
                tot_density.shape))
            logger.debug(
                "Center of mass of total density: {}".format(center_of_mass))

            # take the fourier transform of structure in real_space
            fft_coeff = fftpack.fftn(tot_density,
                                     shape=(self.nx_fft, self.ny_fft,
                                            self.nz_fft))

            # now shift the quadrants around so that low spatial frequencies are in
            # the center of the 2D fourier transformed image.
            fft_coeff_shifted = fftpack.fftshift(fft_coeff)

            # calculate a 3D power spectrum
            power_spect = np.abs(fft_coeff_shifted)**2

            if self.use_mask:
                xc = (self.nx_fft - 1.0) / 2.0
                yc = (self.ny_fft - 1.0) / 2.0
                zc = (self.nz_fft - 1.0) / 2.0

                # spherical mask
                a, b, c = xc, yc, zc
                x, y, z = np.ogrid[-a:self.nx_fft - a, -b:self.ny_fft - b,
                                   -c:self.nz_fft - c]

                mask_int = x * x + y * y + z * z <= self.mask_r_min * self.mask_r_min
                mask_out = x * x + y * y + z * z >= self.mask_r_max * self.mask_r_max

                for i in range(self.nx_fft):
                    for j in range(self.ny_fft):
                        for k in range(self.nz_fft):
                            if mask_int[i, j, k]:
                                power_spect[i, j, k] = 0.0
                            if mask_out[i, j, k]:
                                power_spect[i, j, k] = 0.0

            # cut the spectrum and keep only the relevant part for crystal-structure recognition of
            # hexagonal closed packed (spacegroup=194)
            # simple cubic (spacegroup=221)
            # face centered cubic (spacegroup=225)
            # diamond (spacegroup=227)
            # body centered cubic (spacegroup=229)
            # this interval (20:108) might need to be varied if other classes are added
            power_spect_cut = power_spect[20:108, 20:108, 20:108]
            # zoom by two times using spline interpolation
            power_spect = ndimage.zoom(power_spect_cut, (2, 2, 2))

            if save_diff_intensity:
                np.save(
                    '/home/ziletti/Documents/calc_nomadml/rot_inv_3d/power_spect.npy',
                    power_spect)

            # power_spect.shape = 176, 176, 176
            if plot_3d:
                plot_3d_volume(power_spect)

            vox = np.copy(power_spect)
            logger.debug("nan in data: {}".format(
                np.count_nonzero(~np.isnan(vox))))

            # optimized
            # these specifications are valid for a power_spect = power_spect[20:108, 20:108, 20:108]
            # and a magnification of 2
            xyz_indices_r = get_slice_volume_indices(
                vox,
                min_r=32.0,
                dr=1.0,
                max_r=83.,
                phi_bins=self.phi_bins,
                theta_bins=self.theta_bins)

            # slow - only for benchmarking the fast implementation below (shells_to_sph, interp_theta_phi_surfaces)
            # (vox_by_slices, theta_phi_by_slices) = _slice_3d_volume_slow(vox)

            # convert 3d shells
            (vox_by_slices, theta_phi_by_slices) = get_shells_from_indices(
                xyz_indices_r, vox)
            if plot_slices:
                plot_concentric_shells(
                    vox_by_slices,
                    base_folder=self.configs['io']['main_folder'],
                    idx_slices=None,
                    create_animation=False)

            image_by_slices = interp_theta_phi_surfaces(
                theta_phi_by_slices,
                theta_bins=self.theta_bins_fine,
                phi_bins=self.phi_bins_fine)

            if plot_slices_sph_coords:
                plot_concentric_shells_spherical_coords(
                    image_by_slices,
                    base_folder=self.configs['io']['main_folder'],
                    idx_slices=None)

            coeffs_list = []
            nl_list = []
            ls_list = []

            for idx_slice in range(image_by_slices.shape[0]):
                logger.debug("img #{} max: {}".format(
                    idx_slice, image_by_slices[idx_slice].max()))

                # set to zero the spherical harmonics coefficients above self.sph_l_cutoff
                coeffs = SHExpandDH(image_by_slices[idx_slice], sampling=2)
                coeffs_filtered = coeffs.copy()
                coeffs_filtered[:, self.sph_l_cutoff:, :] = 0.
                coeffs = coeffs_filtered.copy()

                nl = coeffs.shape[0]
                ls = np.arange(nl)
                coeffs_list.append(coeffs)
                nl_list.append(nl)
                ls_list.append(ls)

            coeffs = np.asarray(coeffs_list).reshape(image_by_slices.shape[0],
                                                     coeffs.shape[0],
                                                     coeffs.shape[1],
                                                     coeffs.shape[2])

            sh_coeffs_list = []

            for idx_slice in range(coeffs.shape[0]):
                sh_coeffs = SHCoeffs.from_array(coeffs[idx_slice])
                sh_coeffs_list.append(sh_coeffs)

            sh_spectrum_list = []
            for sh_coeff in sh_coeffs_list:
                sh_spectrum = sh_coeff.spectrum(convention='l2norm')
                sh_spectrum_list.append(sh_spectrum)

            sh_spectra = np.asarray(sh_spectrum_list).reshape(
                coeffs.shape[0], -1)

            # cut the spherical harmonics expansion to sph_l_cutoff order
            logger.debug(
                'Spherical harmonics spectra maximum before normalization: {}'.
                format(sh_spectra.max()))
            sh_spectra = sh_spectra[:, :self.sph_l_cutoff]
            sh_spectra = (sh_spectra - sh_spectra.min()) / (sh_spectra.max() -
                                                            sh_spectra.min())

            # add results in ASE structure info
            descriptor_data = dict(descriptor_name=self.name,
                                   descriptor_info=str(self),
                                   diffraction_3d_sh_spectrum=sh_spectra)

        else:
            # return array with zeros for structures with less than min_nb_atoms
            sh_spectra = np.zeros((52, int(self.sph_l_cutoff)))
            descriptor_data = dict(descriptor_name=self.name,
                                   descriptor_info=str(self),
                                   diffraction_3d_sh_spectrum=sh_spectra)

        structure.info['descriptor'] = descriptor_data

        return structure