-
Notifications
You must be signed in to change notification settings - Fork 49
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
IPF color maps #166
Comments
This is a very good starting point for inverse pole figure (IPF) coloring, @din14970, thanks! We have descriptions of all symmetry operations in the 32 crystallographic point groups in all seven crystal systems in the With that said, I'm almost done with the @din14970, could you paste here the full code you use to plot your IPF coloring? And since we in orix plot x to east, shouldn't your vectors in You should be able to import the classes from orix.quaternion import Rotation
from orix.vector import Vector3d Side-note: Since implementing |
Basically in the first step I am relying on a linear transformation of:
I figured normalizing the vectors would be the most fair, if one does not normalize, and use color_corners = np.array([[0, 1, 1],
[0, 0, 1],
[1, 1, 1]]) So basically by adjusting the length of the vectors one can stretch the color wheel to different colors. In the second bit under
The reason I often do it like this is because my tab-autocomplete in a jupyter notebook finds the things in the actual file or folder, but not those that are imported via
For the stereogram: import numpy as np
from orix.quaternion import Rotation
from orix.vector import Vector3d
from orix.projections import StereographicProjection
from diffsims.generators.rotation_list_generators import get_beam_directions_grid
# grid of euler angles
resolution = 1
grid_cub = get_beam_directions_grid("cubic", resolution, mesh="spherified_cube_edge")
# grid of vectors
grid_stereo = Rotation.from_euler(np.deg2rad(grid_cub))*Vector3d.zvector()
sp = StereographicProjection()
fig, ax = plt.subplots(figsize=(8,8))
ax.set_aspect("equal")
ax.scatter(*sp.vector2xy(grid_stereo), s = 50, c=get_ipf_color(grid_stereo.unit)) # use get_ipf_color for color of points Sidenote: should For the IPF maps it's a similar procedure result = index_dataset_with_template_rotation(data, diff_lib)
rotations = result["austenite"]["orientation"][:,:,0,:] # euler angles as (x, y, 3) array. The 0 is to select the first solution.
vecs_z = Rotation.from_euler(np.deg2rad(rotations))*Vector3d.zvector() # z vectors in a 2D grid to be plot
plt.imshow(get_ipf_color(vecs_z)) # convert vectors to RGB image |
Being able to easily stretch the color key is very important, and should be a feature!
The gridding functions in diffsims are on S2, right? I think these should be in the
No, we keep track of whether vectors are in the current spherical region (for now upper or lower hemisphere) with the When we implement a
@din14970, what do you think of this? |
Yes, I have the constraint that phi1 = 0 in my grid function and so am basically sampling a 3D sphere surface. Sometimes it's hard to see the scope of the different packages, but yes I might agree with you that sampling orientations might fit better in orix. One issue is that I do rely on the orientations being in euler angle format as a numpy array, not an orientation object, for passing it into the template matching algorithm because template matching attempts to find phi1. If I turn it into an orientation and back into euler angles, the first euler angle is often no longer 0 because there are often multiple ways to arrive at the same orientation. How these diffraction libraries are generated with diffsims is quite messy and convoluted in my opinion, this might be a point to work on.
Yep absolutely right, input is orientations/rotations and output is RGB. One should indeed have direction so that IPF-X and Y are easy, or any arbitrary vector really. These things are simple. The challenge is indeed how to map the colors and how to do it for all point groups, and since there appears to be a paper about it it's not so trivial. For no symmetry it's trivial, then it's basically mapping the HSV color cylinder to the sphere. But with the triangles I'm confused about how to properly do it. |
I would prefer that functionality to stay in diffsims for now. |
What I mean is that the spherical gridding functions should be upstreamed to orix, since this is very nice when e.g. generating vectors to create color mappings as @din14970 has done above or like I've done here for point group m-3m (note that Matplotlib in I created the delineating triangle with the new I used your color mapping function for point group m-3m, @din14970, and got these results: The pole figures show basically, in pseudo code, |
Looks cool! Have you compared the colors to what you get with MTEX? |
No, I haven't compared to colors from MTEX yet, because we must take great care that symmetrically equivalent rotations and directions are handled correctly... This isn't far off, but not there yet. My goal is to get #164 done first, then move on to plotting symmetry elements from point groups, basically a |
Having looked at this some more, both what is done in MTEX and in the above referenced paper by Nolze and Hielscher, we can get these colours for point group m-3m for an Ni dataset with orix comparing to MTEX' TSL color key There are subtle differences which I cannot explain, but that is something we can iron out as we start to get PRs in with this functionality. Computing There are a lot of tools (classes/functions/methods) required to obtain this result, mainly:
What's nice is that orix' vector and symmetry classes have almost all we need, apart from the |
The blue dots in the above projection are the vectors before projecting to the fundamental sector. The figure basically shows the inverse pole figure. |
Looks cool!
That's because my way of mapping the colors is done based on the corners and linear algebra, whereas it seems the MTEX way is based on the centroid of the triangle which is mapped to white, and then the HSV color wheel is somehow stretched inside the triangle. I don't have a clue how to implement it the MTEX way but consistency would be nice.
I think this will be much more tricky than expected and I'm not sure whether polar coordinates will be an appropriate parametrization. The (101) and (111) poles are not on the same latitude, so in m-3m ensuring that (101) is pure green and (111) is pure blue with a pure hue gradient in between I think will be very tricky when using a polar coordinate map. Honestly what would you think of just posting on the MTEX forums to ask how they handle the colors? |
sorry, i should have said this, but i didn't use your mapping @din14970, but what is explained in the referenced paper and how it is done in mtex. that also goes for designating hsv colours to the polar coordinates. what im saying is that i have the functionality as simple functions, but i want them structured into classes and more general functions so we can build upon this! |
Ah sorry about that, so you already figured it out then, awesome! |
I should mention that after projecting all rotated vectors to the fundamental region, your coloring isn't too far off MTEX' TSL coloring, @din14970. |
Just to say - this is awesome! |
Hello, can we plot the IPF color codes with the recent version of Orix (however I did not find the relevant class/functions in the master) or this is still under development ? I would very much appreciate this feature in the Orix library. Thank you. |
Hi @ravipurohit1991 ! Unfortunately, we can't do that yet, but we're working on it. As I mention in this issue, there are a few things needed before we can do this: #166 (comment) Thanks for showing interest! |
This would be a great feature! Just to add that it should be possible to use # have some orientations (rot, scipy in this case) from a grid (of size side x side) that represent the standard triangle
xyz = rot.apply((0, 0, 1)).reshape(side, side, 3)
# split into components
x, y, z = xyz[..., 0], xyz[..., 1], xyz[..., 2]
# get grid center points for colour evaluation
x_mid = np.diff(np.diff(x, axis=0), axis=1) + x[:-1, :-1]
y_mid = np.diff(np.diff(y, axis=0), axis=1) + y[:-1, :-1]
z_mid = np.diff(np.diff(z, axis=0), axis=1) + z[:-1, :-1]
xyz_mid = np.column_stack((x_mid.ravel(), y_mid.ravel(), z_mid.ravel()))
c = get_ipf_color(xyz_mid).reshape(*x_mid.shape, 3)
fig, ax = plt.subplots()
# project and show colours
ax.pcolorfast(x / (1 + z), y / (1 + z), c.clip(0, 1)) NB. I have clearly introduced some skew in the orientation grid when trying to create it. If someone knows of a better way that would be great! |
that's a great tip, @harripj! the mesh method should work fine with the StereographicAxes. ill have a look at it. |
alternatively there is also from scipy.interpolate import griddata
from diffsims.generators.rotation_list_generators import get_beam_directions_grid
import matplotlib.colors as mcolors
from orix.projections import StereographicProjection
resolution = 1
grid_cub = get_beam_directions_grid("cubic", resolution, mesh="spherified_cube_edge")
# project a set of vectors to the fundamental zone of the cubic crystal
def to_fundamental(data_sol):
data_sol = np.abs(data_sol)
data_sol = np.sort(data_sol, axis=-1)
column = data_sol[...,0].copy()
data_sol[..., 0] = data_sol[...,1]
data_sol[..., 1] = column
return data_sol
# get the color corresponding to a set of vectors
def get_ipf_color(vectors):
# the following column vectors should map onto R [100], G [010], B[001], i.e. the identity. So the inverse of
# this matrix maps the beam directions onto the right color vector
color_corners = np.array([[0, 1, 1],
[0, 0, 1],
[1, 1, 1]])
color_mapper = np.linalg.inv(color_corners)
# a bit of wrangling
data_sol = to_fundamental(vectors.data)
flattened = data_sol.reshape(np.product(data_sol.shape[:-1]), 3).T
rgb_mapped = np.dot(color_mapper, flattened)
rgb_mapped = np.abs(rgb_mapped / rgb_mapped.max(axis=0)).T
rgb_mapped = rgb_mapped.reshape(data_sol.shape)
return rgb_mapped
# a helper function for turning the euler angle grid to a grid of points in the stereographic projection
def grid_to_xy(grid):
from orix.quaternion.rotation import Rotation
from orix.vector.vector3d import Vector3d
from orix.projections import StereographicProjection
s = StereographicProjection(pole=-1)
rotations_regular = Rotation.from_euler(np.deg2rad(grid))
rot_reg_test = rotations_regular*Vector3d.zvector()
x, y = s.vector2xy(rot_reg_test)
return x, y
# getting the z vector corresponding to an orientation
def ori_to_vec(eulers):
from orix.quaternion.rotation import Rotation
from orix.vector.vector3d import Vector3d
rotations_regular = Rotation.from_euler(np.deg2rad(eulers))
return rotations_regular*Vector3d.zvector()
# xy coordinates of the points in the stereographic projection
xy = np.array(grid_to_xy(grid_cub)).T
colors = get_ipf_color(ori_to_vec(grid_cub))
reds = colors[:, 0]
greens = colors[:, 1]
blues = colors[:, 2]
# here we interpolate between these points on a square grid. We do it for red, green and blue separately
sampling=0.001
gridx, gridy = np.mgrid[-0.05:0.42:sampling, -0.05:0.45:sampling]
t_rd = griddata(xy, reds, (gridy, gridx), method="linear")
t_gn = griddata(xy, greens, (gridy, gridx), method="linear")
t_bl = griddata(xy, blues, (gridy, gridx), method="linear")
t_alpha = np.invert(np.isnan(t_rd)) # everything outside the triangle is black and transparant
t_rd[np.isnan(t_rd)] = 0
t_bl[np.isnan(t_bl)] = 0
t_gn[np.isnan(t_gn)] = 0
triangle = np.stack([t_rd, t_gn, t_bl, t_alpha], axis=-1)
fig, ax1 = plt.subplots()
ax1.imshow(triangle)
ax1.invert_yaxis() |
That looks great! |
Indeed, that looks very nice. We should try to use these functions when plotting the IPF color map reference. |
See the follow-up PR is #235. |
Since the addition of stereographic plots, orix has become a lot more useful also for visualisation. A really nice addition to the visualisation capabilities would be quick IPF maps: I want to be able to iterate on my template matching parameters through feedback I get from a visual, and the most intuitive visualisation are IPF color maps. I have worked out some stuff for my specific case of a cubic crystal, but throwing this as-is into orix is probably not ideal. Nevertheless, I thought I'd throw what I have in here to kickstart a discussion.
In principle mapping vectors to a color is not difficult, but I have yet to figure out how those stereographic triangle colormaps are actually implemented. For the cubic case I came up with something which looks reasonable and close to the map in mtex:
On a cubic grid:
The MTEX color map:
It's not a perfect match, but it does the job.
I can then use this function to map my orientation mapping results to a color image:
I think it would be cool to get something like this working out of the box on orix. Any ideas on how to go about it?
The text was updated successfully, but these errors were encountered: