pyxem / pyxem-demos

Examples and tutorials of multi-dimensional diffraction microscopy workflows using pyxem.
30 stars 38 forks source link

02 - Vector Matching #55

Open edwinsupple opened 3 years ago

edwinsupple commented 3 years ago

I am trying to work through section 4.3 in example notebook 2 in version 0.13 and am having some trouble. In particular, it appears like get_crystallographic_map() is just making another vector object instead of a crystal_map type object. And so then crystal_map doesn't have associated get_phase_map(), get_orientation_map() functions and I am having trouble figuring out how to access the data within crystal_map. Could you please give some guidance on how to use these features?

Thanks,

Edwin

pc494 commented 3 years ago

Hi Edwin,

So, the vector matching workflow (as you've found) is not in a good way. This is for a couple of reasons, primarily due to upstream issues about dealing with ragged arrays and downstream issues to do with orix. I expect the 0.12 version will run okay, but, the following are probably better routes forward.

1) Use the template matching workflow instead if you can. This is better developed within pyxem, and has a recently been improved to give much faster results.

2) I will have a look at exactly what's going on with the issue described over the next couple of days, it may end up being just a small hiccup from the changes introduced to make the code work more nicely with orix. In which case you may well be able to just plug away with it as is. The most cursory of looks suggests that:

def crystal_from_vector_matching(z_matches):
"""Takes vector matching results for a single navigation position and
    returns the best matching phase and orientation with correlation and
    reliability to define a crystallographic map.
    Parameters
    ----------
    z_matches : numpy.array
        Template matching results in an array of shape (m,5) sorted by
        total_error (ascending) within each phase, with entries
        [phase, R, match_rate, ehkls, total_error]
    Returns
    -------
    results_array : numpy.array
        Crystallographic mapping results in an array of shape (3) with entries
        [phase, np.array((z, x, z)), dict(metrics)]
    """

is the function that controls the output of: crystal_map = refined_results.get_crystallographic_map()

if that's the case then: .isig[0] will give you the phase and isig[1] will give you the euler angles. Clearly inelegant, but might help...

3) If you think there is a good reason you want to do vector matching, I have short/medium term plans to rebuild some/all of this workflow, and would be happy to start a dialogue to effect.

edwinsupple commented 3 years ago

I have used the template matching function. Vector matching was appealing because it seems like it might handle arbitrary crystal orientations better than pattern matching.

I also saw you updated the notebook, thanks for doing that. .plot_best_matching_results_on_signal(), though, seems to show that it's mis-identifying the zinc blende DPs as wurtzite DPs. Would it do a better job matching with different parameters entered earlier?

The information about .isig was very helpful, I was able to view the data from crystal_map with a little more work. I'll see how this compares to the pattern matching workflow. Here's what I did to get some data from vector matching, starting from immediately after you use dp.find_peaks():

vectors = DiffractionVectors.from_peaks(peaks, dp.center_of_mass().data, 0.2)
#if you don't use .data it doesn't work properly.

vectors = vectors.inav[1,:10,:5,:] 
#previously vectors didn't have the fourth 2 index long dimension. 
#I think with :10 I am just selecting 10 peaks to index with but I'm not sure about that.

vectors.calculate_cartesian_coordinates(accelerating_voltage=accelarating_voltage,
                                      camera_length=camera_length)

indexation_generator = VectorIndexationGenerator(vectors, vec_lib)

then the rest follows as before. I end up with crystal_map = <VectorMatchingResults, title: , dimensions: (10, 5, 45|3)>

To plot the orientations I did:

def orientation_map(crystal_map):
    """
    Produce an x1 by x2 by 3 np array from a Pyxem VectorMatchingResults object.

    Returns:
    np array with first two dimensions of the source image, and third dimension
    holding x, y, and z Euler angles.

    """
    orientations = crystal_map.isig[1].data
    orientations = orientations.squeeze()

    new_shape = orientations.shape + (3,)
    orientations_data = np.zeros(new_shape)

    for i in range(new_shape[0]):
        for j in range(new_shape[1]):
            orientations_data[i,j,0] = orientations[i,j][0]
            orientations_data[i,j,1] = orientations[i,j][1]
            orientations_data[i,j,2] = orientations[i,j][2]
    return orientations_data

orientations = orientation_map(crystal_map)

fig, ax3 = plt.subplots(1,3)

ax3[0].imshow(orientations[:,:,0])
ax3[1].imshow(orientations[:,:,1])
ax3[2].imshow(orientations[:,:,2])

Attaching an image from the sample data. Seems to be picking up something but not exactly great results. 02 vector matching rotations

pc494 commented 3 years ago

I have used the template matching function. Vector matching was appealing because it seems like it might handle arbitrary crystal orientations better than pattern matching.

I also saw you updated the notebook, thanks for doing that. .plot_best_matching_results_on_signal(), though, seems to show that it's mis-identifying the zinc blende DPs as wurtzite DPs. Would it do a better job matching with different parameters entered earlier?

I think so, yes, the max_excitation_error value is very large for example (which is why you see too many peaks). In checking this I also found a bug in to_crystal_map, which means that the phase map is wrong in this stage (https://github.com/pyxem/pyxem/issues/730). We have a enough things for a patch release, so I'll fix this early next week, hopefully solving this problem for a 0.13.1 version which I can push out the door fairly quickly.

The information about .isig was very helpful, I was able to view the data from crystal_map with a little more work. I'll see how this compares to the pattern matching workflow. Here's what I did to get some data from vector matching, starting from immediately after you use dp.find_peaks():

vectors = DiffractionVectors.from_peaks(peaks, dp.center_of_mass().data, 0.2)
#if you don't use .data it doesn't work properly.

That .data is probably for lazy/non-lazy method reasons.

vectors = vectors.inav[1,:10,:5,:] 
#previously vectors didn't have the fourth 2 index long dimension. 
#I think with :10 I am just selecting 10 peaks to index with but I'm not sure about that.

Might be picking real space pixels, I would check the shapes.

vectors.calculate_cartesian_coordinates(accelerating_voltage=accelarating_voltage,
                                      camera_length=camera_length)

indexation_generator = VectorIndexationGenerator(vectors, vec_lib)

then the rest follows as before. I end up with crystal_map = <VectorMatchingResults, title: , dimensions: (10, 5, 45|3)>

So at a guess I would say this object is:

10 potential matches
5 x dimension
45 y dimension
3 signal bits: phase, array of eulers and dict

And I would start by checking the phase map is reasonable, as plotting euler angles can be very confusing. If that works we can think about tidying up the orientation conversion code, which at a glance looks like it's probably correct.