1+ #include < algorithm>
12#include < array>
23#include < cstdint>
4+ #include < filesystem>
35#include < iostream>
46#include < string>
57#include < utility>
68#include < vector>
79
810#include " EbsdLib/Core/EbsdLibConstants.h"
11+ #include " EbsdLib/IO/HKL/CtfPhase.h"
12+ #include " EbsdLib/IO/HKL/CtfReader.h"
913#include " EbsdLib/IO/TSL/AngPhase.h"
1014#include " EbsdLib/IO/TSL/AngReader.h"
1115#include " EbsdLib/LaueOps/LaueOps.h"
1216#include " EbsdLib/Utilities/ColorTable.h"
1317#include " EbsdLib/Utilities/TiffWriter.h"
1418
15- class Ang2IPF ;
16-
1719using FloatVec3Type = std::array<float , 3 >;
1820
1921using namespace ebsdlib ;
2022
2123/* *
22- * @brief The GenerateIPFColorsImpl class implements a threaded algorithm that computes the IPF
23- * colors for each element in a geometry
24+ * @brief The GenerateIPFColorsImpl class computes the IPF colors for each element in a geometry.
25+ * Uses LaueOps indices directly so it works with both .ang and .ctf phase data.
2426 */
2527class GenerateIPFColorsImpl
2628{
2729public:
28- GenerateIPFColorsImpl (Matrix3X1F& referenceDir, const std::vector<float >& eulers, int32_t * phases, std::vector<AngPhase::Pointer >& crystalStructures , bool * goodVoxels, uint8_t * colors)
30+ GenerateIPFColorsImpl (Matrix3X1F& referenceDir, const std::vector<float >& eulers, int32_t * phases, const std::vector<size_t >& laueOpsIndices , bool * goodVoxels, uint8_t * colors)
2931 : m_ReferenceDir(referenceDir)
3032 , m_CellEulerAngles(eulers)
3133 , m_CellPhases(phases)
32- , m_PhaseInfos(crystalStructures )
34+ , m_LaueOpsIndices(laueOpsIndices )
3335 , m_GoodVoxels(goodVoxels)
3436 , m_CellIPFColors(colors)
3537 {
@@ -46,13 +48,7 @@ class GenerateIPFColorsImpl
4648 int32_t phase = 0 ;
4749 bool calcIPF = false ;
4850 size_t index = 0 ;
49- int32_t numPhases = static_cast <int32_t >(m_PhaseInfos.size ());
50-
51- std::vector<size_t > laueOpsIndex (m_PhaseInfos.size ());
52- for (size_t i = 0 ; i < laueOpsIndex.size (); i++)
53- {
54- laueOpsIndex[i] = m_PhaseInfos[i]->determineOrientationOpsIndex ();
55- }
51+ int32_t numPhases = static_cast <int32_t >(m_LaueOpsIndices.size ());
5652
5753 size_t totalPoints = m_CellEulerAngles.size () / 3 ;
5854 for (size_t i = 0 ; i < totalPoints; i++)
@@ -66,30 +62,24 @@ class GenerateIPFColorsImpl
6662 dEuler[1 ] = m_CellEulerAngles[index + 1 ];
6763 dEuler[2 ] = m_CellEulerAngles[index + 2 ];
6864
69- // Make sure we are using a valid Euler Angles with valid crystal symmetry
7065 calcIPF = true ;
7166 if (nullptr != m_GoodVoxels)
7267 {
7368 calcIPF = m_GoodVoxels[i];
7469 }
75- // Sanity check the phase data to make sure we do not walk off the end of the array
7670 if (phase >= numPhases)
7771 {
78- // m_Filter->incrementPhaseWarningCount();
7972 std::cout << " phase > number of phases" << std::endl;
8073 }
8174
82- size_t currentLaueOpsIndex = laueOpsIndex [phase];
75+ size_t currentLaueOpsIndex = m_LaueOpsIndices [phase];
8376
8477 if (phase < numPhases && calcIPF && currentLaueOpsIndex < ebsdlib::CrystalStructure::LaueGroupEnd)
8578 {
8679 argb = ops[currentLaueOpsIndex]->generateIPFColor (dEuler, refDir, false );
8780 m_CellIPFColors[index] = static_cast <uint8_t >(ebsdlib::RgbColor::dRed (argb));
8881 m_CellIPFColors[index + 1 ] = static_cast <uint8_t >(ebsdlib::RgbColor::dGreen (argb));
8982 m_CellIPFColors[index + 2 ] = static_cast <uint8_t >(ebsdlib::RgbColor::dBlue (argb));
90-
91- // std::cout << (int32_t)(m_CellIPFColors[index]) << "\t" << (int32_t)(m_CellIPFColors[index + 1]) << (int32_t)(m_CellIPFColors[index + 2]) << m_CellEulerAngles[index] << "\t"
92- // << m_CellEulerAngles[index + 1] << "\t" << m_CellEulerAngles[index + 2] << std::endl;
9383 }
9484 }
9585 }
@@ -98,120 +88,181 @@ class GenerateIPFColorsImpl
9888 Matrix3X1F m_ReferenceDir;
9989 const std::vector<float >& m_CellEulerAngles;
10090 int32_t * m_CellPhases;
101- std::vector<AngPhase::Pointer> m_PhaseInfos ;
91+ std::vector<size_t > m_LaueOpsIndices ;
10292
10393 bool * m_GoodVoxels;
10494 uint8_t * m_CellIPFColors;
10595};
10696
10797// -----------------------------------------------------------------------------
108- class Ang2IPF
98+ // Reads a .ang file and generates an IPF color map image.
99+ // -----------------------------------------------------------------------------
100+ int32_t executeAng (const std::string& filepath, const std::string& outputFile, Matrix3X1F& refDir)
109101{
110- public:
111- Ang2IPF ()
102+ AngReader reader;
103+ reader.setFileName (filepath);
104+ int32_t err = reader.readFile ();
105+ if (err < 0 )
112106 {
107+ std::cerr << " Error reading .ang file: " << filepath << std::endl;
108+ return err;
113109 }
114- ~Ang2IPF () = default ;
115110
116- Ang2IPF (const Ang2IPF&) = delete ; // Copy Constructor Not Implemented
117- Ang2IPF (Ang2IPF&&) = delete ; // Move Constructor Not Implemented
118- Ang2IPF& operator =(const Ang2IPF&) = delete ; // Copy Assignment Not Implemented
119- Ang2IPF& operator =(Ang2IPF&&) = delete ; // Move Assignment Not Implemented
111+ std::vector<int32_t > dims = {reader.getXDimension (), reader.getYDimension ()};
112+ size_t totalPoints = reader.getNumberOfElements ();
120113
121- Matrix3X1F m_ReferenceDir = {0 .0f , 0 .0f , 1 .0f };
114+ // Build LaueOps index vector. Insert a dummy at index 0 since ANG phases are 1-based.
115+ std::vector<AngPhase::Pointer> angPhases = reader.getPhaseVector ();
116+ std::vector<size_t > laueOpsIndices;
117+ laueOpsIndices.push_back (0 ); // Dummy for index 0
118+ for (const auto & phase : angPhases)
119+ {
120+ laueOpsIndices.push_back (phase->determineOrientationOpsIndex ());
121+ }
122+
123+ Matrix3X1F normRefDir = refDir.normalize ();
124+
125+ // ANG Euler angles are in radians — interleave into a single array
126+ float * phi1Ptr = reader.getPhi1Pointer (false );
127+ float * phiPtr = reader.getPhiPointer (false );
128+ float * phi2Ptr = reader.getPhi2Pointer (false );
122129
123- /* *
124- * @brief incrementPhaseWarningCount
125- */
126- void incrementPhaseWarningCount ()
130+ std::vector<float > eulers (3 * totalPoints);
131+ for (size_t i = 0 ; i < totalPoints; i++)
127132 {
128- m_PhaseWarningCount++;
133+ eulers[i * 3 ] = phi1Ptr[i];
134+ eulers[i * 3 + 1 ] = phiPtr[i];
135+ eulers[i * 3 + 2 ] = phi2Ptr[i];
129136 }
130137
131- /* *
132- * @brief execute
133- * @return
134- */
135- int32_t execute (const std::string& filepath, const std::string& outputFile)
138+ // Map phase 0 (unindexed) to phase 1
139+ int32_t * phaseData = reader.getPhaseDataPointer (false );
140+ for (size_t i = 0 ; i < totalPoints; i++)
136141 {
137- m_PhaseWarningCount = 0 ;
138- AngReader reader;
139- reader.setFileName (filepath);
140- int32_t err = reader.readFile ();
141- if (err < 0 )
142+ if (phaseData[i] < 1 )
142143 {
143- return err ;
144+ phaseData[i] = 1 ;
144145 }
146+ }
145147
146- std::vector<int32_t > dims = {reader.getXDimension (), reader.getYDimension ()};
148+ bool * goodVoxels = nullptr ;
149+ std::vector<uint8_t > ipfColors (totalPoints * 3 , 0 );
150+ GenerateIPFColorsImpl generateIPF (normRefDir, eulers, phaseData, laueOpsIndices, goodVoxels, ipfColors.data ());
151+ generateIPF.run ();
147152
148- size_t totalPoints = reader.getNumberOfElements ();
149- std::vector<AngPhase::Pointer> crystalStructures = reader.getPhaseVector ();
150- crystalStructures.emplace (crystalStructures.begin (), AngPhase::New ());
151- // int32_t numPhase = static_cast<int32_t>(crystalStructures.size());
153+ auto error = TiffWriter::WriteColorImage (outputFile, dims[0 ], dims[1 ], 3 , ipfColors.data ());
154+ if (error.first < 0 )
155+ {
156+ std::cerr << error.second << std::endl;
157+ }
158+ return error.first ;
159+ }
152160
153- // Make sure we are dealing with a unit 1 vector.
154- Matrix3X1F normRefDir = m_ReferenceDir.normalize (); // Make a copy of the reference Direction and normalize it
161+ // -----------------------------------------------------------------------------
162+ // Reads a .ctf file and generates an IPF color map image.
163+ // CTF Euler angles are in degrees and must be converted to radians.
164+ // -----------------------------------------------------------------------------
165+ int32_t executeCtf (const std::string& filepath, const std::string& outputFile, Matrix3X1F& refDir)
166+ {
167+ CtfReader reader;
168+ reader.setFileName (filepath);
169+ int32_t err = reader.readFile ();
170+ if (err < 0 )
171+ {
172+ std::cerr << " Error reading .ctf file: " << filepath << std::endl;
173+ return err;
174+ }
155175
156- float * phi1Ptr = reader.getPhi1Pointer (false );
157- float * phiPtr = reader.getPhiPointer (false );
158- float * phi2Ptr = reader.getPhi2Pointer (false );
176+ std::vector<int32_t > dims = {reader.getXDimension (), reader.getYDimension ()};
177+ size_t totalPoints = reader.getNumberOfElements ();
159178
160- // We need to interleave the phi1, PHI, phi2 data into a single 3 component array
161- std::vector<float > eulers (3 * totalPoints);
179+ // Build LaueOps index vector. Insert a dummy at index 0 since CTF phases are 1-based.
180+ std::vector<CtfPhase::Pointer> ctfPhases = reader.getPhaseVector ();
181+ std::vector<size_t > laueOpsIndices;
182+ laueOpsIndices.push_back (0 ); // Dummy for index 0
183+ for (const auto & phase : ctfPhases)
184+ {
185+ laueOpsIndices.push_back (phase->determineOrientationOpsIndex ());
186+ }
162187
163- for (size_t i = 0 ; i < totalPoints; i++)
164- {
165- eulers[i * 3 ] = phi1Ptr[i];
166- eulers[i * 3 + 1 ] = phiPtr[i];
167- eulers[i * 3 + 2 ] = phi2Ptr[i];
168- }
188+ Matrix3X1F normRefDir = refDir.normalize ();
169189
170- int32_t * phaseData = reader.getPhaseDataPointer (false );
171- for (size_t i = 0 ; i < totalPoints; i++)
172- {
173- if (phaseData[i] < 1 )
174- {
175- phaseData[i] = 1 ;
176- }
177- }
190+ // CTF Euler angles are in degrees — convert to radians and interleave
191+ float * euler1Ptr = reader.getEuler1Pointer ();
192+ float * euler2Ptr = reader.getEuler2Pointer ();
193+ float * euler3Ptr = reader.getEuler3Pointer ();
194+ const float degToRad = static_cast <float >(ebsdlib::constants::k_DegToRadD);
178195
179- bool * goodVoxels = nullptr ;
180- std::vector<uint8_t > ipfColors (totalPoints * 3 , 0 );
181- GenerateIPFColorsImpl generateIPF (normRefDir, eulers, phaseData, crystalStructures, goodVoxels, ipfColors.data ());
182- generateIPF.run ();
196+ std::vector<float > eulers (3 * totalPoints);
197+ for (size_t i = 0 ; i < totalPoints; i++)
198+ {
199+ eulers[i * 3 ] = euler1Ptr[i] * degToRad;
200+ eulers[i * 3 + 1 ] = euler2Ptr[i] * degToRad;
201+ eulers[i * 3 + 2 ] = euler3Ptr[i] * degToRad;
202+ }
183203
184- std::pair< int32_t , std::string> error = TiffWriter::WriteColorImage (outputFile, dims[ 0 ], dims[ 1 ], 3 , ipfColors. data ());
185- if (error. first < 0 )
186- {
187- std::cout << error. second << std::endl;
188- }
189- return error. first ;
204+ // Map phase 0 (unindexed) to phase 1
205+ int * phaseData = reader. getPhasePointer ();
206+ std::vector< int32_t > phases (totalPoints);
207+ for ( size_t i = 0 ; i < totalPoints; i++)
208+ {
209+ phases[i] = (phaseData[i] < 1 ) ? 1 : phaseData[i] ;
190210 }
191211
192- private:
193- int32_t m_PhaseWarningCount = {0 };
194- };
212+ bool * goodVoxels = nullptr ;
213+ std::vector<uint8_t > ipfColors (totalPoints * 3 , 0 );
214+ GenerateIPFColorsImpl generateIPF (normRefDir, eulers, phases.data (), laueOpsIndices, goodVoxels, ipfColors.data ());
215+ generateIPF.run ();
216+
217+ auto error = TiffWriter::WriteColorImage (outputFile, dims[0 ], dims[1 ], 3 , ipfColors.data ());
218+ if (error.first < 0 )
219+ {
220+ std::cerr << error.second << std::endl;
221+ }
222+ return error.first ;
223+ }
195224
196225// -----------------------------------------------------------------------------
197226int main (int argc, char * argv[])
198227{
199-
200228 if (argc != 3 )
201229 {
202- std::cout << " Program needs file path to .ang file and output image file " << std::endl;
230+ std::cout << " Usage: make_ipf <input_file .ang|input_file.ctf> <output_image.tiff> " << std::endl;
203231 return 1 ;
204232 }
205- std::cout << " WARNING: This program makes NO attempt to fix the sample and crystal reference frame issue that is common on TSL systems." << std::endl;
233+
234+ std::cout << " WARNING: This program makes NO attempt to fix the sample and crystal reference frame issue." << std::endl;
206235 std::cout << " WARNING: You are probably *not* seeing the correct colors. Use something like DREAM.3D to fully correct for these issues." << std::endl;
236+
207237 std::string filePath (argv[1 ]);
208238 std::string outPath (argv[2 ]);
239+
240+ // Determine file type from extension
241+ std::string ext = std::filesystem::path (filePath).extension ().string ();
242+ std::transform (ext.begin (), ext.end (), ext.begin (), [](unsigned char c) { return static_cast <char >(std::tolower (c)); });
243+
244+ Matrix3X1F referenceDir = {0 .0f , 0 .0f , 1 .0f };
245+
209246 std::cout << " Creating IPF Color Map for " << filePath << std::endl;
210247
211- Ang2IPF Ang2IPF;
212- if (Ang2IPF.execute (filePath, outPath) < 0 )
248+ int32_t result = -1 ;
249+ if (ext == " .ang" )
250+ {
251+ result = executeAng (filePath, outPath, referenceDir);
252+ }
253+ else if (ext == " .ctf" )
254+ {
255+ result = executeCtf (filePath, outPath, referenceDir);
256+ }
257+ else
258+ {
259+ std::cerr << " ERROR: Unsupported file extension '" << ext << " '. Use .ang or .ctf" << std::endl;
260+ return 1 ;
261+ }
262+
263+ if (result < 0 )
213264 {
214- std::cout << " Error creating the IPF Color map" << std::endl;
265+ std::cerr << " Error creating the IPF Color map" << std::endl;
215266 }
216- return 0 ;
267+ return result < 0 ? 1 : 0 ;
217268}
0 commit comments