GithubHelp home page GithubHelp logo

pyxem / diffsims Goto Github PK

View Code? Open in Web Editor NEW
46.0 7.0 26.0 6.52 MB

An open-source Python library providing utilities for simulating diffraction

Home Page: https://diffsims.readthedocs.io

License: GNU General Public License v3.0

Python 100.00%
diffraction physics python electron-diffraction physics-simulation

diffsims's Introduction

Actions Coveralls pypi_version downloads black doi

pyxem is an open-source (GPL v3) python library for multi-dimensional diffraction microscopy.

The package defines objects and functions for the analysis of numerous diffraction patterns. It has been primarily developed as a platform for hybrid diffraction-microscopy based on 4D scanning diffraction microscopy data in which a 2D diffraction pattern is recorded at every position in a 2D scan of a specimen.

pyxem is an extension of the hyperspy library for multi-dimensional data analysis and defines diffraction specific Signal classes.

Installation instructions and tutorial examples are available here .

Basic Documentation is available here.

If analysis using pyxem forms a part of published work please cite the DOI at the top of this page. In addition to citing the package we would appreciate an additional citation to methods papers if you use the following capabilities:

Orientation Mapping

@article{pyxemorientationmapping2022,
    title={Free, flexible and fast: Orientation mapping using the multi-core and GPU-accelerated template matching capabilities in the python-based open source 4D-STEM analysis toolbox Pyxem},
    author={Cautaerts, Niels and Crout, Phillip and {\AA}nes, H{\aa}kon Wiik and Prestat, Eric and Jeong, Jiwon and Dehm, Gerhard and Liebscher, Christian H},
    journal={Ultramicroscopy},
    pages={113517},
    year={2022},
    publisher={Elsevier},
    doi={10.1016/j.ultramic.2022.113517}
}

Strain Mapping

Two-Dimensional Strain Mapping with Scanning Precession Electron Diffraction: An Investigation into Data Analysis Routines by Crout et al. which is freely avaliable at https://arxiv.org/abs/2307.01071

diffsims's People

Contributors

agborrelli avatar cssfrancis avatar din14970 avatar dnjohnstone avatar eirikopheim avatar ericpre avatar hakonanes avatar isabelwood100 avatar jmorzy avatar joonatanl avatar norskie avatar pc494 avatar phillipcrout avatar pre-commit-ci[bot] avatar robtovey avatar shogas avatar smc204 avatar stefsmeets avatar tiarnan-doherty avatar tinabe avatar to266 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

diffsims's Issues

Noise models for simulations

Add some functions to add noise to simulations. E.g:

def _addnoise(x, alpha, mu, sigma):
"""Add Poisson-Gaussian noise to the data x

Parameters
----------
x : float
    The original data

alpha : float
    Level of noise gain

mu : float
    Level of noise offset

sigma : float
    Level of Gaussian noise

Returns
-------
y : float
    The corrupted data

"""
y = alpha * np.random.poisson(x / alpha) + mu + sigma * np.random.randn()
return y

def add_poisson_gaussian_noise(X, alpha, mu, sigma):
"""Add Poisson-Gaussian noise to the data X

Parameters
----------
X : array
    The data to be corrupted

alpha : float
    Level of noise gain

mu : float
    Level of noise offset

sigma : float
    Level of Gaussian noise

Returns
-------
Y : array
    The corrupted data

"""
# Do some error checking
if alpha < 0. or alpha > 1.:
    raise ValueError("alpha should be in range [0,1]")
# Vectorize noise function
addnoise = np.vectorize(_addnoise, otypes=[np.float])

# Rescale to [0,1] range
Xmax = np.amax(X)
X = X / Xmax

# Add noise
Y = addnoise(X, alpha, mu, sigma)

# Rescale to [0,1] range
Y = Y + np.abs(np.amin(Y))

# Rescale to X range
Y = Xmax * Y / np.amax(Y)
return Y

def _gamma_noise_model(x, a, mu):
    """Calculates the expected variance of a pixel based on its value `x`"""
    return a * (x / mu) * np.exp(- x / mu)

def add_parameterized_gamma_noise(self, a=75., mu=10.):
    """Adds noise based on a custom model to the data."""
    noise = np.random.normal(0., self._gamma_noise_model(self.data, a, mu)**0.5)
    original_dtype = self.data.dtype
    self.data = (
        self.data.astype(noise.dtype) + noise
    ).astype(original_dtype)
    self.events.data_changed.trigger(obj=self)

Bug in calculate_profile_data

calculate_profile_data - calculates a one dimensional diffraction profile for a structure.

Describe the bug
It does not return the correct multiplicity of generated planes

Problem is in line 237: peaks[g_hkl] = [i_hkl, [tuple(hkl)], d_hkl]

Planes from the same family have the same spacing in reciprocal space (g_hkl) which means that planes from the same family overwrite each other.

Possible solution
Instead of defining a dictionary of peaks, create list:
peaks.append([g_hkl, i_hkl, [tuple(hkl)], d_hkl]) and then feed peaks[2] into get_unique_families. Currently working on that.

Notes
Have tested get_unique_families separately and that function seems to work fine.

Probe functions & aberrations

There are some probe function things here, that we should think about supporting: https://github.com/ePSIC-DLS/epsic_tools/pull/15/files

E.g. aberration function:

def FuncAberrUV(u,v,aberrcoeff):

# this function needs comments

# input:
# u: Kx
# v: Ky

u2 = u*u
u3 = u2*u
u4 = u3*u

v2 = v*v
v3 = v2*v
v4 = v3*v

# aberr are in unit of meter.
C1   = aberrcoeff[0] # defocus
C12a = aberrcoeff[1] # 2 stig
C12b = aberrcoeff[2] # 2 stig
C23a = aberrcoeff[3] # 3 stig
C23b = aberrcoeff[4] # 3 stig
C21a = aberrcoeff[5] # coma 
C21b = aberrcoeff[6] # coma
C3   = aberrcoeff[7] # Spherical abb
C34a = aberrcoeff[8] # 4 stig
C34b = aberrcoeff[9] # 4 stig
C32a = aberrcoeff[10] # star
C32b = aberrcoeff[11] # star

# output:  chi function. in unit of meter*radian.  multiply by 2pi/lambda to get dimensionless
func_aberr =  1/2*C1*(u2+v2)\
        + 1/2*(C12a*(u2-v2) + 2*C12b*u*v)\
        + 1/3*(C23a*(u3-3*u*v2) + C23b*(3*u2*v - v3))\
        + 1/3*(C21a*(u3+u*v2) + C21b*(v3+u2*v))\
        + 1/4* C3*(u4+v4+2*u2*v2)\
        + 1/4* C34a*(u4-6*u2*v2+v4)\
        + 1/4* C34b*(4*u3*v-4*u*v3)\
        + 1/4* C32a*(u4-v4)\
        + 1/4* C32b*(2*u3*v + 2*u*v3)\

return func_aberr

Kinematic simulator needs a revist + a sinc function

Describe the solution you'd like
In the short term to have two options, either sinc or linear for the relrod decay, in the medium term allow for more complex shape functions.

Update, July 2020:

Looping back round to this, in the short term we are going to include a binary solution for the excitation error. I'll then close this an open a new issue for further developments.

test_kinematical_simulator needs sorting

The test_kinematical_simulator tests in test_sim_utils.py do not have proper assertions at the moment. They should, and indeed the kinematical simulator should be checked better.

Around "beam direction" functionality

This stems from a discussion on #47

Two types of functionality that involve user input "beam directions" are desired for diffsims.

  1. A way to take a known structure and generate zone axis patterns (ZAPs) #7
  2. The generation of a rotation list about a certain beam direction

In (1) the user will have to provide a diffpy structure, this includes a metric tensor (or at least the parts needed to create one) which in turn allows for calculations of appropriate euler angles

In (2) no such structure needs to be provided (and nor should it), but without that simply saying "I want to be down a [110]" isn't sufficient, the rotation to a cubic [110] is not the same as the one to an orthorhombic [110].

My proposed solution

  • Make get_grid_around_beam_direction() take an euler triplet as it's main argument.
  • Write a util that takes a structure and a desired beam direction and returns an euler triplet eg:
    get_rotation_to_beam_direction(structure,beam_direction)
    that takes into account the crystal metric tensor
  • Use the above util for a ZAP map function

Simplify getting atomic scattering parameters

Is your feature request related to a problem? Please describe.
There are now multiple parametrizations of atomic scattering parameters in the code:

Describe the solution you'd like

  • All parametrizations should be placed in one (it's own?) module (directory).
  • The module's location should probably be in the diffsims.structure_factor module, which makes most sense?
  • The module should have one public function taking in preferably a diffpy.structure.Atom with an element ID in Atom.element (like "H", "Ni", etc.), a string or an integer, and which parametrization of the above to use. The parameters are returned.

save/load functions need overhaul

Saving/loading is currently pretty ugly - only defined for library objects and just done by some pretty basic pickle-ing. Now that diffsims is separate from pyxem it's more obvious that this needs to be addressed properly.

Memory usage in template library generation

Template libraries can get very large if all symmetry inequivalent diffraction patterns are simulated for multiple low-symmetry crystal structures. We need to come up with a more memory efficient way of producing and using such libraries.

Bug in get_grid_around_beam_direction()

Describe the bug
get_grid_around_beam_direction() works for beam directions [1,0,0] and [0,1,0] but not for [001]

To Reproduce
Steps to reproduce the behavior:
from diffsims.generators.rotation_list_generators import get_grid_around_beam_direction rot_list = get_grid_around_beam_direction([0,0,1], 5)

Expected behavior
Should work for the family of directions <1,0,0>.

Screenshots
Traceback:
https://imgur.com/a/IDa8Ogc

Kinematical CBED simulation & disc overlap calculation

It would be good to add a simple kinematical simulation of a CBED disk pattern and allow simple calculation of overlap between discs.

This should be fairly easy to do by taking the output of the basic simulator and applying a convolution with a top hat function.

The following code snippet from @chrisallen80 would give a way to calculate the percentage overlap of neighbouring discs, which we could initialize with the disc centers from our current kinematical simulator perhaps.

import numpy as np
r = 4e-10 #probe radius
d = 0.73e-10 # probe spacing
x_pos  = 0.5 * d # x coordinate of circle intersection
y_pos = np.sqrt(r**2 - x_pos**2) # y coordinate of circle intersection
theta = 2*np.arctan(y_pos / x_pos) # angle subtended by overlap
A_overlap =  (r**2 * theta) - (2*x_pos*y_pos) # area of overlap
A_probe = np.pi * r**2
Percentage_overlap =100 *  A_overlap / A_probe
print('x, y : ', x_pos, y_pos)
print('theta : ', theta )
print('overlap area : ', A_overlap)
print('probe area : ', A_probe)
print('overlap  % : ', Percentage_overlap)

Minor enhancement for function get_points_in_sphere (sim_util)

Minor Enhancement:
For the function get_points_in_sphere(reciprocal_lattice, reciprocal_radius) in diffsims/sim_util I think we can replace ceil() with floor() and save some evaluations that will not pass the following test of being inside the reciprocal radius anyways. This is as the furthest we can go from the center in discrete steps along the a-axis should be floor(a/reciprocal_radius).

I am not sure how much time this will save, but this function seems to be called a lot, so maybe it can save a few minutes for big projects. If we worry about missing a point due to machine precision round-off error, I guess we could write floor(a/reciprocal_radius + pow(10,-14)).

get_grid_stereographic() for rotation_list_generators seems to have a bug for input ('cubic'

Describe the bug
The function get_grid_stereographic() for rotation_list_generators seems to have a bug for input ('cubic'). By exporting every orientation in the orientation list and plotting in MTEX I get the following picture:

cubic2083randofAll_Z
There is an area that is not covered close to the [-1 1 1] corner.
cubic2083randofAll
The x-projection looks fine.

To Reproduce
Steps to reproduce the behavior:

  1. Run rotation_list_generators.get_grid_stereographic()
  2. Export list of rotations to MTEX
  3. Plot orientations in MTEX using plotIPDF

Expected behavior
I believe the full z-projection should be covered.

Screenshots
For option 'orthorhombic' there is no issue:
orthorhombic12500ofAll
orthorhombic12500ofAll_X

Furthermore, (and I am not sure if this is an issue) if I project the rotation list for symmetry (-43m) I get the following result:
Cubic_m43mz
Good apart from the issue near the corner.
Cubic_m43mx
Is this an oversampling?
Should only half the IPF be covered?

Possible bug in get_orientations_from_stereographic_triangle()

I am not sure if this is really a bug or if I am not using the function correctly. I have interpreted the function as giving a list of orientations that span the Euler space for a given crystal structure.

What I have done to test this function is generate a grid of all Euler angles from (-90,-90,0) to (90,90,0) at 5 degree intervals, and simulate diffraction patterns of gold (cubic fcc) for each orientation.
I experience that for my simulated data, if I use a set of orientations from get_orientation_from_stereographic_triangle() in my template library, using a resolution of 1/2 degree, the match is a good fit only in the region characterized by having as corners euler angles (0,-55,0) (-45,-55,0)(-45,55,0)(0,55,0)[I think first angle is along y axis.] And a similar region on top in the image. Outside this range templates do not fit the simulated diffraction maps.

Figure__Signal_Pyxem_stereographic_triangle

To see if I could find the minimum set of orientations necessary for the template to do the job I have made a template library with the angles in the grid. I find that if I use angles from (-45,-90,0) to (45,90,0) I get a unique match for each pixel, except along the lines (mod(x,90) = 0, : ,0), where I have double matches. See figure

Figure__Signal_pyxem_orientation_list

Steps to reproduce the behavior:

  1. Generate a simulated set of diffraction patterns for known angles at a regular grid in the range (-90,-90,0) to (90,90,0).
  2. Use the same set of angles as a template, but include only angles from (-45,-90,0) to (45,90,0)
  3. Perform template matching. Plot orientation map. Use this this as a "perfect match".
  4. Generate angles from get_orientations_from_stereographic_triangle() at high resolution.
  5. Perform template matching and plot orientation map. See problem.

I would expect that the orientation map obtained using get_orientations_from_stereographic_triangle() would be similar to the "perfect match" all over, with some errors, but in general a smooth result. It seems to me like the Euler space is not covered in get_orientations_from_stereographic_triangle().

Possible solutions:

  1. Maybe I have to choose the in-plane rotations in a specific way?
  2. Span orientation angles over a the rectangle I have been using

Note: The reason why I choose a smooth orientation map as a quality metric is that if there are multiple correct templates, it seems like the template will somewhat randomly be choosen, as seen in the lines in figure 2. Also, for a distance metric such as the one generating the orientation map the triangle inequality D(a,b) <= D(a,c)+D(c,b) should ensure a smooth orientation map, if the orientation map is correct. If the distance between two neighbouring pixels is more than 5 i suppose one of them must be wrong, as their relative tilt is 5 degrees.

pyxem 0.10.0.

Rotations conventions seem inconsistent with diffpy's

Describe the bug
We use transform3d for our rotation matrices, which are applied on the left [1].
In simulate_rotated_structure we pass one such matrix to diffpy, and it is applied on the right [2]

This means that the (alpha,beta,gamma) that is eventually returned (from say a template match) is not "correct" but is (I think) consistent, as the error propagates in a "safe" way.

[1] https://matthew-brett.github.io/transforms3d/conventions.html
[2] https://www.diffpy.org/diffpy.structure/mod_lattice.html#diffpy.structure.lattice.Lattice.base

Black code style

We will adopt black code formatting in v0.3.0 - I will deal with this.

bugfix for get_grid_beam_directions

Describe the bug
This code is poorly written, and possibly incorrect (#103), although it's unused at present it would be good to tidy it up. However, it may be worth sitting on until we can do it with orix code (or similar)

When should we rotate our structures?

Describe the bug
simulate_rotated_structure(), in theory, "rotates" a diffpy lattice. In practice ... it's not that simple.

To Reproduce

Make a boring orthorhombic lattice.
Make a second "rotated" lattice - the rotation here is a cut down version of what we do in simulate_rotated_structure()

canonical = diffpy.structure.lattice.Lattice(4,2,1, 90, 90, 90)
R = np.asarray([[1/np.sqrt(2),1/np.sqrt(2),0],[-1/np.sqrt(2),1/np.sqrt(2),0],[0,0,1]])
halfhalf  = diffpy.structure.lattice.Lattice(base=([email protected]))

The problem is that the halfhalf lattice has two cell parameters of 3.162... ~ this makes life hard work.

Solution
Cut all the rotation code that involved diffpy, spit cartesians out of get_points_sphere() then
manually rotate the cartesians with a rotation matrix. I actually think this might be quite simple.
get_kinematical_intensities might also need a revisit

Credits for Rob Tovey

A good portion of our simulation code comes from @robtovey, but isn't ascribed to him by github yet. It would be nice to fix this.

diffsims-0.3.0

diffsims-0.3.0 will depend on orix-0.4.0 and be a platform for future releases of both pyxem and kikuchipy.

In this release we will address a number of long standing issues with diffsims and improve the architecture significantly.

This will include:

  • Removing sampling of orientation space from diffsims to inherit from orix #104
  • Add the ReciprocalLatticePoints class, as a platform for unifying simulators. #111
  • Implementing more physical shape factors #23 and #114

This will include significant contributions from several people, particularly including @JoonatanL @hakonanes and @pc494. This issue is raised to plan the development and to try and ensure that everyone is pulling in the same direction.

Please comment any suggestions, modifications to this, etc.

Simulator architecture and API in diffsims

In #111 @hakonanes asked about where the new ReciprocalLatticePoint or CrystalPlane class will be used in diffsims. This reminded me that we need to look at the overall architecture for different types of diffraction simulation in diffsims, which follows on from an offline conversation with @pc494 and @JoonatanL.

To summarize the current types of simulators in diffsims, we have:

The calculate_ed_data method of DiffractionGenerator computes a spot electron diffraction pattern by evaluating the Structure Factor Fhkl for relevant crystal planes, code is here:

The calculate_profile_data method of DiffractionGenerator computes a one dimensional electron (powder type) electron diffraction pattern based on evaluating the Structure Factor Fhkl for relevant families of crystal planes, code is here:

def calculate_profile_data(

The calculate_ed_data method of AtomicDiffractionGenerator computes a single crystal spot/disk like electron diffraction pattern for a specified probe function and crystal structure by evaluating the FFT of the crystal potential and probe function. This is similar to one slice of a multislice code, code is here:

The simulate_kinematic_scattering function computes a single-crystal electron diffraction pattern for an arbitrary arrangement of atoms under either plane wave or Gaussian probe illumination by evaluating the structure factor of the arbitrary atomic arrangment. In othe words the atomic scattering factor of every atom in the user defined object is summed to simulate diffraction, code is here:

def simulate_kinematic_scattering(

The last two approaches are very helpful because they take account of the object shape properly if you're simulating diffraction from a nanocrystal and they can also be used to simulate scattering from non-crystalline or defective materials. The FFT approach has some advantages in terms of computational efficiency, but direct summation of atomic scattering factors has the advantage that arbitrarily small displacements of atomic coordinates are easy to incorporate.

In short, I think we want all of these as well as adding:

  • Kikuchi diffraction
  • A multislice simulator
  • A bloch wave simulator

Clearly the API here is a total mess and I think we should tear it up and start again after #111 is merged and we re-evaluate these simulators in any case.

I think the people mentioned above have some interest in this and hopefully this helps let everyone know some stuff that is probably only in my head right now.

In writing this down, it strikes me that @CSSFrancis might have some interest too.

implement RingPatternGenerator

We have some code to make a ring pattern in pyxem calibration_utils.py - we should put it in diffsims really and generalize to any structure:

def calc_radius_with_distortion(x, y, xc, yc, asym, rot):
    """ calculate the distance of each 2D point from the center (xc, yc) """
    xp = x * np.cos(rot) - y * np.sin(rot)
    yp = x * np.sin(rot) + y * np.cos(rot)
    xcp = xc * np.cos(rot) - yc * np.sin(rot)
    ycp = xc * np.sin(rot) + yc * np.cos(rot)

    return np.sqrt((xp - xcp) ** 2 + asym * (yp - ycp) ** 2)


def call_ring_pattern(xcenter, ycenter):
    """
    Function to make a call to the function ring_pattern without passing the
    variables directly (necessary for using scipy.optimize.curve_fit).

    Parameters
    ----------
    xcenter : float
        The coordinate (fractional pixel units) of the diffraction
        pattern center in the first dimension
    ycenter : float
        The coordinate (fractional pixel units) of the diffraction
        pattern center in the second dimension

    Returns
    -------
    ring_pattern : function
        A function that calculates a ring pattern given a set of points and
        parameters.

    """

    def ring_pattern(
        pts, scale, amplitude, spread, direct_beam_amplitude, asymmetry, rotation
    ):
        """Calculates a polycrystalline gold diffraction pattern given a set of
        pixel coordinates (points). It uses tabulated values of the spacings
        (in reciprocal Angstroms) and relative intensities of rings derived from
        X-ray scattering factors.

        Parameters
        -----------
        pts : 1D array
            One-dimensional array of points (first half as first-dimension
            coordinates, second half as second-dimension coordinates)
        scale : float
            An initial guess for the diffraction calibration
            in 1/Angstrom units
        amplitude : float
            An initial guess for the amplitude of the polycrystalline rings
            in arbitrary units
        spread : float
            An initial guess for the spread within each ring (Gaussian width)
        direct_beam_amplitude : float
            An initial guess for the background intensity from
            the direct beam disc in arbitrary units
        asymmetry : float
            An initial guess for any elliptical asymmetry in the pattern
            (for a perfectly circular pattern asymmetry=1)
        rotation : float
            An initial guess for the rotation of the (elliptical) pattern
            in radians.

        Returns
        -------
        ring_pattern : np.array()
            A one-dimensional array of the intensities of the ring pattern
            at the supplied points.

        """
        rings = [0.4247, 0.4904, 0.6935, 0.8132, 0.8494, 0.9808, 1.0688, 1.0966]
        rings = np.multiply(rings, scale)
        amps = [1, 0.44, 0.19, 0.16, 0.04, 0.014, 0.038, 0.036]

        x = pts[: round(np.size(pts, 0) / 2)]
        y = pts[round(np.size(pts, 0) / 2) :]
        Ri = calc_radius_with_distortion(x, y, xcenter, ycenter, asymmetry, rotation)

        v = []
        denom = 2 * spread ** 2
        v.append(direct_beam_amplitude * Ri ** -2)  # np.exp((-1*(Ri)*(Ri))/d0)
        for i in [0, 1, 2, 3, 4, 5, 6, 7]:
            v.append(amps[i] * np.exp((-1 * (Ri - rings[i]) * (Ri - rings[i])) / denom))

        return (
            amplitude
            * (v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6] + v[7] + v[8]).ravel()
        )

    return ring_pattern


def generate_ring_pattern(
    image_size,
    mask=False,
    mask_radius=10,
    scale=100,
    amplitude=1000,
    spread=2,
    direct_beam_amplitude=500,
    asymmetry=1,
    rotation=0,
):
    """Calculate a set of rings to model a polycrystalline gold diffraction
    pattern for use in fitting for diffraction pattern calibration.
    It is suggested that the function generate_ring_pattern is used to
    find initial values (initial guess) for the parameters used in
    the function fit_ring_pattern.

    This function is written expecting a single 2D diffraction pattern
    with equal dimensions (e.g. 256x256).

    Parameters
    ----------
    mask : bool
        Choice of whether to use mask or not (mask=True will return a
        specified circular mask setting a region around
        the direct beam to zero)
    mask_radius : int
        The radius in pixels for a mask over the direct beam disc
        (the direct beam disc within given radius will be excluded
        from the fit)
    scale : float
        An initial guess for the diffraction calibration
        in 1/Angstrom units
    image_size : int
        Size of the diffraction pattern to be generated in pixels.
    amplitude : float
        An initial guess for the amplitude of the polycrystalline rings
        in arbitrary units
    spread : float
        An initial guess for the spread within each ring (Gaussian width)
    direct_beam_amplitude : float
        An initial guess for the background intensity from the
        direct beam disc in arbitrary units
    asymmetry : float
        An initial guess for any elliptical asymmetry in the pattern
        (for a perfectly circular pattern asymmetry=1)
    rotation : float
        An initial guess for the rotation of the (elliptical) pattern
        in radians.

    Returns
    -------
    image : np.array()
        Simulated ring pattern with the same dimensions as self.data

    """
    xi = np.linspace(0, image_size - 1, image_size)
    yi = np.linspace(0, image_size - 1, image_size)
    x, y = np.meshgrid(xi, yi)

    pts = np.array([x.ravel(), y.ravel()]).ravel()
    xcenter = (image_size - 1) / 2
    ycenter = (image_size - 1) / 2

    ring_pattern = call_ring_pattern(xcenter, ycenter)
    generated_pattern = ring_pattern(
        pts, scale, amplitude, spread, direct_beam_amplitude, asymmetry, rotation
    )
    generated_pattern = np.reshape(generated_pattern, (image_size, image_size))

    if mask == True:
        maskROI = calc_radius_with_distortion(
            x, y, (image_size - 1) / 2, (image_size - 1) / 2, 1, 0
        )
        maskROI[maskROI > mask_radius] = 0
        generated_pattern[maskROI > 0] *= 0

    return generated_pattern

Add Kikuchi lines to simulations

It would be nice to add Kikuchi lines to basic simulations using a simple geometric model. Perhaps starting with zone axis geometries only.

@hakonanes - don't know if you know of any packages already doing this?

Small changes for patch versions

These things are too small to be their own issues.

  • Have the correct sections of code ascribed to @robtovey on github (is now #97)
  • Tidy up the syntax inside the streographic generator (is now #93)
  • Reorder the plt.figure() statement in the docstrings for the zap map
  • Remove gimbal lock rotations from the euler generator (#82)
  • Check the euler generator gives good lists (maybe #82)
  • Prepare the refactorisation of excitation error to a structure/call of calculate_ed_data()
  • Prepare the refacotrisation allowing more complicated excitation error functions (#23)

Adding get_grid_around_beam_direction

Describe the bug
This functionality (which was in v0.2) relied on now deleted architecture and was set to raise an error in #104, it should be a fairly simple orix wrap and we should probably do it for v0.3

ZAP map generator

It is often useful to simulate electron diffraction from various zone axes i.e. to produce a zone axis pattern (ZAP) map. A generator for this application would be useful.

Further rotation implementation problems

Describe the bug
Following on from #46 further issues have been found with the rotation elements of the code. The presented example was that (-60,60,0) and (30,60,0) produce identical diffraction patterns.

Expected behavior
Using orix or similar you can find that these two rotations differ by a [0,0,1] 90 degree rotation. We would expect them to have different diffraction patterns (the produced patterns don't have 4 fold sym). Extracting the code from simulate_rotated_structure() we (@EirikOpheim and then I via a different work flow) find that the real space lattices are compliant with this.

This pushes us down the chain to calculate_ed_data() which uses the .reciprocal() of our (correct) inputs. It seems here is where the problem is.

Detail the problem
Calling get_point_in_sphere() an looking through the elements we find that for rotated structures the a,b and c values are different. The way the code is laid out what you really want to happen is the indexing to be done in the "normal" orientation and then the final results to be rotated.

Assuming the cartesian outputs are correct the rest of the code doesn't need to worry about the rotations.

Solution
The following seems to work better.

standard_base = diffpy.structure.lattice.Lattice(base=reciprocal_lattice.stdbase)
# changed from using reciprocal_lattice
a, b, c = standard_base.a, standard_base.b, standard_base.c
...
potential_points = np.asarray(list(product(h_list, k_list, l_list)))
#standard here rather than reciprocal
in_sphere = np.abs(standard_base.dist(potential_points, [0, 0, 0])) < reciprocal_radius
spot_indicies = potential_points[in_sphere]
# back to reciprocal for the cartesian conversion
spot_coords = reciprocal_lattice.cartesian(spot_indicies)

Running two cases (no rotation vs small [0.2rad ~ 11degrees] z rotation) supports this:


# same in both cases
spot_indicies =
array([[-1.,  0.,  0.],
       [ 0., -1.,  0.],
       [ 0.,  0., -1.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  1.],
       [ 0.,  1.,  0.],
       [ 1.,  0.,  0.]])
no_rotation_coords = 
array([[-0.33333333,  0.        ,  0.        ],
       [ 0.        , -0.33333333,  0.        ],
       [ 0.        ,  0.        , -0.33333333],
       [ 0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.33333333],
       [ 0.        ,  0.33333333,  0.        ],
       [ 0.33333333,  0.        ,  0.        ]])
# our lattice parameter was 3
small_rotation_coords = 
array([[-0.32668886,  0.06622311,  0.        ],
       [-0.06622311, -0.32668886,  0.        ],
       [ 0.        ,  0.        , -0.33333333],
       [ 0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.33333333],
       [ 0.06622311,  0.32668886,  0.        ],
       [ 0.32668886, -0.06622311,  0.        ]])

I plan on implementing this in the coming days.

Further thoughts
This section of the code could really do with some semi-physical testing. I think this should be fairly high on the v0.4 list.

move utils from pyxem/pixstem into diffsims

These functions currently in pyxem.utils.diffraction_tools should be moved to diffsims.

import numpy as np
import scipy.constants as sc


def acceleration_voltage_to_wavelength(acceleration_voltage):
    """Get electron wavelength from the acceleration voltage.

    Parameters
    ----------
    acceleration_voltage : float or array-like
        In Volt

    Returns
    -------
    wavelength : float or array-like
        In meters

    """
    E = acceleration_voltage*sc.elementary_charge
    h = sc.Planck
    m0 = sc.electron_mass
    c = sc.speed_of_light
    wavelength = h/(2*m0*E*(1 + (E/(2*m0*c**2))))**0.5
    return wavelength


def diffraction_scattering_angle(
        acceleration_voltage, lattice_size, miller_index):
    """Get electron scattering angle from a crystal lattice.

    Returns the total scattering angle, as measured from the middle of the
    direct beam (0, 0, 0) to the given Miller index.

    Miller index: h, k, l = miller_index
    Interplanar distance: d = a / (h**2 + k**2 + l**2)**0.5
    Bragg's law: theta = arcsin(electron_wavelength / (2 * d))
    Total scattering angle (phi):  phi = 2 * theta

    Parameters
    ----------
    acceleration_voltage : float
        In Volt
    lattice_size : float or array-like
        In meter
    miller_index : tuple
        (h, k, l)

    Returns
    -------
    angle : float
        Scattering angle in radians.

    """
    wavelength = acceleration_voltage_to_wavelength(acceleration_voltage)
    H, K, L = miller_index
    a = lattice_size
    d = a/(H**2 + K**2 + L**2)**0.5
    scattering_angle = 2 * np.arcsin(wavelength / (2 * d))
    return scattering_angle

Improving streographic triangle functionality

Our current streographic_triangle_implementation was designed to work with an older pyxem architecture. To update it I propose the following.

Create a function get_beam_direction_streographic(density,crystal_system)
which more or less replicates (with bugfixes) the existing functionality.

Add a second function, get_rotation_list_streographic(density,beam_resolution,crystal_system) that behaves in a manner that is consistent with other rotation_list generators.

EMsoft now has kinematical PED simulations and Python wrappers for some modules

I haven't tried any of the functionality mentioned below out yet, but posted this as an fyi. The issue can be closed at your convenience.

The newly released EMsoft version 4.3 can simulate kinematical PED patterns via the EMPEDkin routine (https://github.com/EMsoft-org/EMsoft/blob/f5e6c8a5cb186478f5d34597708d4962f245a794/Source/TEM/EMPEDkin.f90).

Also included are Python wrappers for a lot of the modules available via a sub directory in the main repo named pyEMsoft, see these links:

  1. https://github.com/EMsoft-org/EMsoft/wiki/python-examples
  2. https://github.com/EMsoft-org/EMsoft/wiki/python-wrapper-installation
  3. https://github.com/EMsoft-org/EMsoft/blob/develop/Source/pyEMsoft/docs/pyEMsoft.rst

Make better use of crystallographic libraries

We depend on diffpy.structure for atomic structure manipulations but perhaps currently under-utilise some of the functionality of that package.

One example is that the is_lattice_hexagonal() function in sim_utils is probably superseded by the isSpaceGroupLatPar() function in SymmetryUtilities in diffpy.

Another case is when we need dot products, angles etc - we should be using Lattice.angle, Lattice.norm etc from diffpy.structure.

This mostly requires us getting a bit more familiar with diffpy.structure and should have the advantage of making things more robust and more general moving forwards.

This may involve implementing a space group analyzer e.g. https://github.com/materialsproject/pymatgen/blob/master/pymatgen/symmetry/analyzer.py for diffpy structures.

That functionality depends mostly on spglib https://atztogo.github.io/spglib/python-spglib.html#python-spglib

Note we should also consider use and dependence on other crystallographic libraries

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.