LCOV - code coverage report
Current view: top level - src/utils - Euler2RGB.C (source / functions) Hit Total Coverage
Test: idaholab/moose phase_field: #31405 (292dce) with base fef103 Lines: 57 61 93.4 %
Date: 2025-09-04 07:55:36 Functions: 1 1 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "Euler2RGB.h"
      11             : #include "MathUtils.h"
      12             : #include "libmesh/utility.h"
      13             : 
      14             : /**
      15             :  * This function rotates a set of three Bunge Euler angles into
      16             :  * the standard Stereographic triangle, interpolates the RGB color
      17             :  * value based on a user selected reference sample direction, and
      18             :  * outputs an integer value representing the RGB tuplet usable for
      19             :  * plotting inverse pole figure colored grain maps.  The program
      20             :  * can accommodate any of the seven crystal systems.
      21             :  *
      22             :  *  Inputs:   1) Reference sample direction (sd) input as an integer between
      23             :  *               1 and 3.  Options are [100], [010], and [001] with [001]
      24             :  *               being the most common choice found in the literature. Input
      25             :  *               "1" to select [100], "2" to select [010], and "3" to select
      26             :  *               the [001] sample direction.
      27             :  *            2) Set of three Euler angles (phi1, PHI, phi2) in the Bunge
      28             :  *               notation (must be in radians)
      29             :  *            3) Phase number "phase" used to assign black color values to
      30             :  *               voids (phase = 0)
      31             :  *            4) Integer value of crystal class as defined by OIM Software:
      32             :  *                    "43" for cubic
      33             :  *                    "62" for hexagonal
      34             :  *                    "42" for tetragonal
      35             :  *                    "32" for trigonal
      36             :  *                    "22" for orthorhombic
      37             :  *                     "2" for monoclinic
      38             :  *                     "1" for triclinic
      39             :  *                     "0" for unindexed points (bad data point)
      40             :  *
      41             :  *  Output:  Integer value of RGB calculated from:
      42             :  *           RGB = red*256^2 + green*256 + blue (where red, green, and blue
      43             :  *           are integer values between 0 and 255)
      44             :  */
      45             : Point
      46      150034 : euler2RGB(unsigned int sd, Real phi1, Real PHI, Real phi2, unsigned int phase, unsigned int sym)
      47             : {
      48             :   // Define Constants
      49             :   const Real pi = libMesh::pi;
      50             :   const Real pi_x2 = 2.0 * pi;
      51             :   const Real a = std::sqrt(3.0) / 2.0;
      52             : 
      53             :   // Preallocate and zero variables
      54             :   unsigned int index = 0;
      55             :   unsigned int nsym = 1;
      56             : 
      57             :   Real blue = 0.0;
      58             :   Real chi = 0.0;
      59             :   Real chi_min = 0.0;
      60             :   Real chi_max = 0.0;
      61             :   Real chi_max2 = 0.0;
      62             :   Real eta = 0.0;
      63             :   Real eta_min = 0.0;
      64             :   Real eta_max = 0.0;
      65             :   Real green = 0.0;
      66             :   Real maxRGB = 0.0;
      67             :   Real red = 0.0;
      68             : 
      69      150034 :   unsigned int ref_dir[3] = {0, 0, 0};
      70      150034 :   Real g[3][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
      71      150034 :   Real hkl[3] = {0.0, 0.0, 0.0};
      72      150034 :   Real S[3][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
      73             :   const Real(*SymOps)[3][3];
      74             : 
      75             :   Point RGB;
      76             : 
      77             :   // Assign reference sample direction
      78      150034 :   switch (sd)
      79             :   {
      80           4 :     case 1: // 100
      81           4 :       ref_dir[0] = 1;
      82             :       ref_dir[1] = 0;
      83             :       ref_dir[2] = 0;
      84           4 :       break;
      85             : 
      86           0 :     case 2: // 010
      87             :       ref_dir[0] = 0;
      88           0 :       ref_dir[1] = 1;
      89             :       ref_dir[2] = 0;
      90           0 :       break;
      91             : 
      92      150030 :     case 3: // 001
      93             :       ref_dir[0] = 0;
      94             :       ref_dir[1] = 0;
      95      150030 :       ref_dir[2] = 1;
      96      150030 :       break;
      97             :   };
      98             : 
      99             :   // Define symmetry operators for each of the seven crystal systems
     100      150034 :   const Real SymOpsCubic[24][3][3] = {
     101             :       {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},    {{0, 0, 1}, {1, 0, 0}, {0, 1, 0}},
     102             :       {{0, 1, 0}, {0, 0, 1}, {1, 0, 0}},    {{0, -1, 0}, {0, 0, 1}, {-1, 0, 0}},
     103             :       {{0, -1, 0}, {0, 0, -1}, {1, 0, 0}},  {{0, 1, 0}, {0, 0, -1}, {-1, 0, 0}},
     104             :       {{0, 0, -1}, {1, 0, 0}, {0, -1, 0}},  {{0, 0, -1}, {-1, 0, 0}, {0, 1, 0}},
     105             :       {{0, 0, 1}, {-1, 0, 0}, {0, -1, 0}},  {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
     106             :       {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1}},  {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
     107             :       {{0, 0, -1}, {0, -1, 0}, {-1, 0, 0}}, {{0, 0, 1}, {0, -1, 0}, {1, 0, 0}},
     108             :       {{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}},   {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}},
     109             :       {{-1, 0, 0}, {0, 0, -1}, {0, -1, 0}}, {{1, 0, 0}, {0, 0, -1}, {0, 1, 0}},
     110             :       {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}},   {{-1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
     111             :       {{0, -1, 0}, {-1, 0, 0}, {0, 0, -1}}, {{0, 1, 0}, {-1, 0, 0}, {0, 0, -1}},
     112             :       {{0, 1, 0}, {1, 0, 0}, {0, 0, -1}},   {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}}};
     113             : 
     114      150034 :   const Real SymOpsHexagonal[12][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     115             :                                           {{-0.5, a, 0}, {-a, -0.5, 0}, {0, 0, 1}},
     116             :                                           {{-0.5, -a, 0}, {a, -0.5, 0}, {0, 0, 1}},
     117             :                                           {{0.5, a, 0}, {-a, 0.5, 0}, {0, 0, 1}},
     118             :                                           {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1}},
     119             :                                           {{0.5, -a, 0}, {a, 0.5, 0}, {0, 0, 1}},
     120             :                                           {{-0.5, -a, 0}, {-a, 0.5, 0}, {0, 0, -1}},
     121             :                                           {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
     122             :                                           {{-0.5, a, 0}, {a, 0.5, 0}, {0, 0, -1}},
     123             :                                           {{0.5, a, 0}, {a, -0.5, 0}, {0, 0, -1}},
     124             :                                           {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
     125             :                                           {{0.5, -a, 0}, {-a, -0.5, 0}, {0, 0, -1}}};
     126             : 
     127      150034 :   const Real SymOpsTetragonal[8][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     128             :                                           {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
     129             :                                           {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}},
     130             :                                           {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1}},
     131             :                                           {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1}},
     132             :                                           {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}},
     133             :                                           {{0, 1, 0}, {1, 0, 0}, {0, 0, -1}},
     134             :                                           {{0, -1, 0}, {-1, 0, 0}, {0, 0, -1}}};
     135             : 
     136      150034 :   const Real SymOpsTrigonal[6][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     137             :                                         {{-0.5, a, 0}, {-a, -0.5, 0}, {0, 0, 1}},
     138             :                                         {{-0.5, -a, 0}, {a, -0.5, 0}, {0, 0, 1}},
     139             :                                         {{0.5, a, 0}, {a, -0.5, 0}, {0, 0, -1}},
     140             :                                         {{-1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     141             :                                         {{0.5, -a, 0}, {-a, -0.5, 0}, {0, 0, -1}}};
     142             : 
     143      150034 :   const Real SymOpsOrthorhombic[4][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     144             :                                             {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}},
     145             :                                             {{1, 0, 0}, {0, -1, 0}, {0, 0, 1}},
     146             :                                             {{-1, 0, 0}, {0, -1, 0}, {0, 0, 1}}};
     147             : 
     148      150034 :   const Real SymOpsMonoclinic[2][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}},
     149             :                                           {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}}};
     150             : 
     151      150034 :   const Real SymOpsTriclinic[1][3][3] = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
     152             : 
     153             :   // Assign parameters based on crystal class (sym)
     154             :   // Load cubic parameters (class 432)
     155             :   if (sym == 43)
     156             :   {
     157             :     nsym = 24;
     158             :     SymOps = SymOpsCubic;
     159             :     eta_min = 0 * (pi / 180);
     160             :     eta_max = 45 * (pi / 180);
     161             :     chi_min = 0 * (pi / 180);
     162             :     chi_max = std::acos(std::sqrt(1.0 / (2.0 + (Utility::pow<2>(std::tan(eta_max))))));
     163             :   }
     164             : 
     165             :   //  Load hexagonal parameters (class 622)
     166             :   else if (sym == 62)
     167             :   {
     168             :     nsym = 12;
     169             :     SymOps = SymOpsHexagonal;
     170             :     eta_min = 0 * (pi / 180);
     171             :     eta_max = 30 * (pi / 180);
     172             :     chi_min = 0 * (pi / 180);
     173             :     chi_max = pi / 2;
     174             :   }
     175             : 
     176             :   //  Load tetragonal parameters (class 422)
     177             :   else if (sym == 42)
     178             :   {
     179             :     nsym = 8;
     180             :     SymOps = SymOpsTetragonal;
     181             :     eta_min = 0 * (pi / 180);
     182             :     eta_max = 45 * (pi / 180);
     183             :     chi_min = 0 * (pi / 180);
     184             :     chi_max = pi / 2;
     185             :   }
     186             : 
     187             :   //  Load trigonal parameters (class 32)
     188             :   else if (sym == 32)
     189             :   {
     190             :     nsym = 6;
     191             :     SymOps = SymOpsTrigonal;
     192             :     eta_min = 0 * (pi / 180);
     193             :     eta_max = 60 * (pi / 180);
     194             :     chi_min = 0 * (pi / 180);
     195             :     chi_max = pi / 2;
     196             :   }
     197             : 
     198             :   //  Load orthorhombic parameters (class 22)
     199             :   else if (sym == 22)
     200             :   {
     201             :     nsym = 4;
     202             :     SymOps = SymOpsOrthorhombic;
     203             :     eta_min = 0 * (pi / 180);
     204             :     eta_max = 90 * (pi / 180);
     205             :     chi_min = 0 * (pi / 180);
     206             :     chi_max = pi / 2;
     207             :   }
     208             : 
     209             :   //  Load monoclinic parameters (class 2)
     210             :   else if (sym == 2)
     211             :   {
     212             :     nsym = 2;
     213             :     SymOps = SymOpsMonoclinic;
     214             :     eta_min = 0 * (pi / 180);
     215             :     eta_max = 180 * (pi / 180);
     216             :     chi_min = 0 * (pi / 180);
     217             :     chi_max = pi / 2;
     218             :   }
     219             : 
     220             :   //  Load triclinic parameters (class 1)
     221             :   else if (sym == 1)
     222             :   {
     223             :     nsym = 1;
     224             :     SymOps = SymOpsTriclinic;
     225             :     eta_min = 0 * (pi / 180);
     226             :     eta_max = 360 * (pi / 180);
     227             :     chi_min = 0 * (pi / 180);
     228             :     chi_max = pi / 2;
     229             :   }
     230             : 
     231             :   // Accomodate non-conforming (bad) data points
     232             :   else
     233             :   {
     234             :     nsym = 0;
     235             :   }
     236             : 
     237             :   // Start of main routine //
     238             :   // Assign black RGB values for bad data points (nsym = 0) or voids (phase = 0)
     239      150034 :   if (nsym == 0 || phase == 0)
     240         258 :     RGB = 0;
     241             : 
     242             :   // Assign black RGB value for Euler angles outside of allowable range
     243      149776 :   else if (phi1 > pi_x2 || PHI > pi || phi2 > pi_x2)
     244           0 :     RGB = 0;
     245             : 
     246             :   //  Routine for valid set of Euler angles
     247             :   else
     248             :   {
     249             :     // Construct 3X3 orientation matrix
     250      149776 :     g[0][0] = std::cos(phi1) * std::cos(phi2) - std::sin(phi1) * std::cos(PHI) * std::sin(phi2);
     251      149776 :     g[0][1] = std::sin(phi1) * std::cos(phi2) + std::cos(phi1) * std::cos(PHI) * std::sin(phi2);
     252      149776 :     g[0][2] = std::sin(phi2) * std::sin(PHI);
     253      149776 :     g[1][0] = -std::cos(phi1) * std::sin(phi2) - std::sin(phi1) * std::cos(PHI) * std::cos(phi2);
     254      149776 :     g[1][1] = -std::sin(phi1) * std::sin(phi2) + std::cos(phi1) * std::cos(PHI) * std::cos(phi2);
     255      149776 :     g[1][2] = std::cos(phi2) * std::sin(PHI);
     256      149776 :     g[2][0] = std::sin(phi1) * std::sin(PHI);
     257      149776 :     g[2][1] = -std::cos(phi1) * std::sin(PHI);
     258      149776 :     g[2][2] = std::cos(PHI);
     259             : 
     260             :     // Loop to sort Euler angles into standard stereographic triangle (SST)
     261             :     index = 0;
     262     1480525 :     while (index < nsym)
     263             :     {
     264             :       // Form orientation matrix
     265     5922100 :       for (unsigned int i = 0; i < 3; ++i)
     266    17766300 :         for (unsigned int j = 0; j < 3; ++j)
     267             :         {
     268    13324725 :           S[i][j] = 0.0;
     269    53298900 :           for (unsigned int k = 0; k < 3; ++k)
     270    39974175 :             S[i][j] += SymOps[index][i][k] * g[k][j];
     271             :         }
     272             : 
     273             :       // Multiple orientation matrix by reference sample direction
     274     5922100 :       for (unsigned int i = 0; i < 3; ++i)
     275             :       {
     276     4441575 :         hkl[i] = 0;
     277    17766300 :         for (unsigned int j = 0; j < 3; ++j)
     278    13324725 :           hkl[i] += S[i][j] * ref_dir[j];
     279             :       }
     280             : 
     281             :       // Convert to spherical coordinates (ignore "r" variable since r=1)
     282     1480525 :       eta = std::abs(std::atan2(hkl[1], hkl[0]));
     283     1480525 :       chi = std::acos(std::abs(hkl[2]));
     284             : 
     285             :       // Continue if eta and chi values are within the SST
     286     1480525 :       if (eta >= eta_min && eta < eta_max && chi >= chi_min && chi < chi_max)
     287             :         break;
     288             : 
     289             :       // Increment to next symmetry operator if not in SST
     290             :       else
     291     1330749 :         index++;
     292             : 
     293             :       // Check for solution
     294             :       mooseAssert(index != nsym, "Euler2RGB failed to map the supplied Euler angle into the SST!");
     295             :     }
     296             : 
     297             :     //  Adjust maximum chi value to ensure it falls within the SST (cubic materials only)
     298      149776 :     if (sym == 43)
     299      149775 :       chi_max2 = std::acos(std::sqrt(1.0 / (2.0 + (Utility::pow<2>(std::tan(eta))))));
     300             :     else
     301             :       chi_max2 = pi / 2;
     302             : 
     303             :     // Calculate the RGB color values and make adjustments to maximize colorspace
     304      149776 :     red = std::abs(1.0 - (chi / chi_max2));
     305      149776 :     blue = std::abs((eta - eta_min) / (eta_max - eta_min));
     306      149776 :     green = 1.0 - blue;
     307             : 
     308      149776 :     blue = blue * (chi / chi_max2);
     309      149776 :     green = green * (chi / chi_max2);
     310             : 
     311             :     // Check for negative RGB values before taking square root
     312             :     mooseAssert(red >= 0 || green >= 0 || blue >= 0, "RGB component values must be positive!");
     313             : 
     314      149776 :     RGB(0) = std::sqrt(red);
     315      149776 :     RGB(1) = std::sqrt(green);
     316      149776 :     RGB(2) = std::sqrt(blue);
     317             : 
     318             :     // Find maximum value of red, green, or blue
     319      149776 :     maxRGB = std::max({RGB(0), RGB(1), RGB(2)});
     320             : 
     321             :     // Normalize position of SST center point
     322             :     RGB /= maxRGB;
     323             :   }
     324             : 
     325      150034 :   return RGB;
     326             : }

Generated by: LCOV version 1.14