Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to use euler fundamental region for mean orientation #437

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

anderscmathisen
Copy link
Contributor

@anderscmathisen anderscmathisen commented Mar 14, 2023

Description of the change

Added override to the mean() function in the Orientation class with the option to calculate the mean in the euler fundamental region. Also keeps the symmetry of the Orientation for the returned mean. This way, when plotting in an IPF, I think the mean Orientation makes more sense.

Progress of the PR

Minimal example of the bug fix or new feature

import orix.plot
from orix.quaternion import Orientation
from orix.quaternion.symmetry import D6h
from orix.vector import Vector3d
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

oris = Orientation(
[[ 0.215 ,  0.9696, -0.1001, -0.0602],
 [ 0.2132 , 0.97  , -0.0977, -0.0643],
 [ 0.2154 , 0.9693 ,-0.098 , -0.067 ],
 [ 0.2157 , 0.9692, -0.0989, -0.0659],
 [ 0.2141 , 0.9693 ,-0.1012, -0.0668],
 [ 0.2132 , 0.9696, -0.1001, -0.0659],
 [ 0.2083 , 0.9715 ,-0.0952, -0.0605],
 [ 0.1558 , 0.7913 ,-0.569 , -0.1608],
 [ 0.1495 , 0.79   ,-0.5704, -0.1681],
 [ 0.1537 , 0.7908, -0.5686, -0.1663],
 [ 0.1552 , 0.7918 ,-0.5677, -0.1634],
 [ 0.1518 , 0.792 , -0.5679, -0.165 ],
 [ 0.0484 , 0.3993, -0.8886, -0.2203],
 [ 0.0459 , 0.4007, -0.888,  -0.2209]], symmetry = D6h)

fig = plt.figure(figsize=(9,5))

gs = GridSpec(2,3)

ax_x_not_fundamental = fig.add_subplot(gs[0,0], projection = "ipf", direction = Vector3d.xvector(), symmetry = D6h)
ax_y_not_fundamental = fig.add_subplot(gs[0,1], projection = "ipf", direction = Vector3d.yvector(), symmetry = D6h)
ax_z_not_fundamental = fig.add_subplot(gs[0,2], projection = "ipf", direction = Vector3d.zvector(), symmetry = D6h)

ax_x_fundamental = fig.add_subplot(gs[1,0], projection = "ipf", direction = Vector3d.xvector(), symmetry = D6h)
ax_y_fundamental = fig.add_subplot(gs[1,1], projection = "ipf", direction = Vector3d.yvector(), symmetry = D6h)
ax_z_fundamental = fig.add_subplot(gs[1,2], projection = "ipf", direction = Vector3d.zvector(), symmetry = D6h)

ax_x_not_fundamental.set_title("X")
ax_y_not_fundamental.set_title("Not in fundamental region\nY")
ax_z_not_fundamental.set_title("Z")

ax_x_fundamental.set_title("X")
ax_y_fundamental.set_title("In fundamental region\nY")
ax_z_fundamental.set_title("Z")

mean_ori_not_in_fundamental = oris.mean()
mean_ori_in_fundamental = oris.mean(mean_in_euler_fundamental_region=True)

ax_x_not_fundamental.scatter(oris, label ="Data")
ax_y_not_fundamental.scatter(oris)
ax_z_not_fundamental.scatter(oris)
ax_x_not_fundamental.scatter(mean_ori_not_in_fundamental, c = "r", label = "Mean")
ax_y_not_fundamental.scatter(mean_ori_not_in_fundamental, c = "r")
ax_z_not_fundamental.scatter(mean_ori_not_in_fundamental, c = "r")

ax_x_fundamental.scatter(oris)
ax_y_fundamental.scatter(oris)
ax_z_fundamental.scatter(oris)
ax_x_fundamental.scatter(mean_ori_in_fundamental, c = "r")
ax_y_fundamental.scatter(mean_ori_in_fundamental, c = "r")
ax_z_fundamental.scatter(mean_ori_in_fundamental, c = "r")


fig.tight_layout()
handles, labels = ax_x_not_fundamental.get_legend_handles_labels()
handle_list, label_list = [], []
for handle, label in zip(handles, labels):
    if label in ["sa_circle", "sa_sector"]:
        continue
    if label not in label_list:
        handle_list.append(handle)
        label_list.append(label)
fig.legend(handle_list, label_list, loc = "upper left", fontsize = 12)

Orientation

For reviewers

  • The PR title is short, concise, and will make sense 1 year later.
  • New functions are imported in corresponding __init__.py.
  • New features, API changes, and deprecations are mentioned in the unreleased
    section in CHANGELOG.rst.
  • Contributor(s) are listed correctly in __credits__ in orix/__init__.py and in
    .zenodo.json.

@hakonanes
Copy link
Member

hakonanes commented Mar 14, 2023

Thanks for this, @anderscmathisen.

Did you consider using Orientation.map_into_symmetry_reduced_zone().mean() instead? I would expect this to give correct results. But then I checked, and it does not... I knew there were some inconsistencies between this function and the other projection tools in orix. This has been bothering me for a while, and I believe we should double check this implementation before continuing with this PR.

I actually have a rewritten version of EMsoft's (Singh and De Graef's) reduction to the fundamental zone, and it produces the same results as you get by projecting to the Euler fundamental region. Interestingly, it reduces all your orientations above to a tight cluster in the axis-angle space of 622. Is this as expected?

For completeness' sake, this change also relates to #434 in that we should overwrite more methods in the Orientation class to better handle properties (like symmetry).

@hakonanes hakonanes added the enhancement New feature or request label Mar 14, 2023
@hakonanes hakonanes added this to the v0.12.0 milestone Mar 14, 2023
@anderscmathisen
Copy link
Contributor Author

anderscmathisen commented Mar 14, 2023

Thanks for the swift response.

I indeed initially tried to use map_into_symmetry_reduced_zone(), but when it did not provide the desired results, I choose the euler fundamental zone instead.

I agree that the map_into_symmetry_reduced_zone() would be more natural to use here. I dont yet really understand how that function is supposed to work, and thus have not figured out why it does not. When plotting oris from the code above in 3D (oris.map_into_symmetry_reduced_zone().scatter()) the orientations appear to be left untouched e.g. same as oris.scatter(). It appears to me that Misorientation.map_into_symmetry_reduced_zone() is coded to work best with Misorientations and perhaps not the child Orientation? A possible (quick) fix is to overwrite that function in the Orientation class using in_euler_fundamental_region()? But map_into_symmetry_reduced_zone() is perhaps supposed to be fundamentally different from in_euler_fundamental_region()? I expected them to be the same.

I actually have a rewritten version of EMsoft's (Singh and De Graef's) reduction to the fundamental zone, and it produces the same results as you get by projecting to the Euler fundamental region. Interestingly, it reduces all your orientations above to a tight cluster in the axis-angle space of 622. Is this as expected?

Not really no.

Edit:
My implementation here does not fix the example problem in #434, so I guess something is wrong and map_into_symmetry_reduces_zone()should be used. However this would not work for my example here...

@hakonanes
Copy link
Member

Not really no.

OK, I checked my results against EMsoft's using their command line program EMConvertOrientations, converting quaternions to quaternions using point group 6/mmm and reducing them to the fundamental zone. EMsoft gives three clusters, as the current implementation in orix (although they are different...). My implementation misses something, which I'll fix. We can hopefully then update the current implementation of map_into_symmetry_reduced_zone(). This should then fix both your issue here and the one in #434.

@hakonanes
Copy link
Member

I've changed reduction to the Rodrigues fundamental zone in #442. This change should fix your issue here, i.e. if you pull that branch and replace

mean_ori_in_fundamental = oris.mean(mean_in_euler_fundamental_region=True)

with

mean_ori_in_fundamental = oris.reduce().mean()

you should get what you want.

I would be very grateful if you could actually check out the change on your example here and also any other problems you might have where reducing to the fundamental zone would be desirable.

@anderscmathisen
Copy link
Contributor Author

Hi,

I have been testing with #442 and indeed that seems to solve all the issues with mean orientation :)

As we discussed offline @hakonanes, we should rebase this PR to #442 and replace my previously committed code with something along the lines of:

def mean(self, reduce=True):
    """..."""
    if reduce:
        quat = Quaternion(self.reduce())
    else:
        quat = Quaternion(self)
    return Orientation(quat.mean(), symmetry = self.symmetry)

It is perhaps best to wait for #442 to merge into develop before rebasing and continuing with this PR?

@hakonanes
Copy link
Member

Great. Yes, since we might make some changes to #442 before merging.

@hakonanes
Copy link
Member

hakonanes commented Apr 16, 2023

BTW, since Misorientation.reduce() will be updated to align with expectations in #442 as well, I suggest to upstream your change here to Misorientation.mean():

def mean(self, reduce=True):
    """..."""
    if reduce:
        quat = Quaternion(self.reduce())
    else:
        quat = Quaternion(self)
    return self.__class__(quat.mean(), symmetry=self.symmetry)

@hakonanes hakonanes removed this from the v0.12.0 milestone May 30, 2023
@hakonanes hakonanes removed the enhancement New feature or request label May 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants