Skip to content

Commit f781c7d

Browse files
imikejacksonclaude
andcommitted
ENH: Update make_ipf to support both .ang and .ctf file formats
Detects file type from extension. CTF Euler angles are converted from degrees to radians. Refactored GenerateIPFColorsImpl to use LaueOps indices directly instead of AngPhase::Pointer, so it works with both file formats. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5324ddf commit f781c7d

1 file changed

Lines changed: 143 additions & 92 deletions

File tree

Source/Apps/make_ipf.cpp

Lines changed: 143 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
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-
1719
using FloatVec3Type = std::array<float, 3>;
1820

1921
using 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
*/
2527
class GenerateIPFColorsImpl
2628
{
2729
public:
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
// -----------------------------------------------------------------------------
197226
int 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

Comments
 (0)