Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b0d864f
feat: add ColorSpaceUtils with HSL/HSV/RGB conversions
imikejackson Mar 24, 2026
e59e42a
feat: add IColorKey abstract interface for pluggable IPF color strate…
imikejackson Mar 24, 2026
bf68031
feat: add FundamentalSectorGeometry with polar coordinate computation…
imikejackson Mar 24, 2026
f1d27b0
feat: extract TSLColorKey from LaueOps::computeIPFColor
imikejackson Mar 24, 2026
2fdcb47
feat: implement Nolze-Hielscher IPF color key algorithm from paper
imikejackson Mar 24, 2026
2db73ce
feat: integrate pluggable IColorKey into LaueOps with TSL default
imikejackson Mar 24, 2026
4459dd8
feat: add tests verifying IPF legend generation works with Nolze-Hiel…
imikejackson Mar 24, 2026
d45f3f5
feat: implement extended color key for non-mirror Laue groups (m-3, 6…
imikejackson Mar 24, 2026
65714ca
feat: add tests for impossible coloring mode (triclinic, trigonal-low)
imikejackson Mar 24, 2026
9007d98
feat: generate_ipf_legends app produces both TSL and Nolze-Hielscher …
imikejackson Mar 24, 2026
fb6879e
fix: correct lightness mapping direction in NolzeHielscherColorKey
imikejackson Mar 24, 2026
01f35c8
fix: add Gaussian hue correction and improve saturation in NH color key
imikejackson Mar 24, 2026
5f614bb
fix: implement boundary-distance-weighted azimuthal correction
imikejackson Mar 24, 2026
ea7451a
fix: use orix/MTEX polar coordinate convention to eliminate gray bands
imikejackson Mar 24, 2026
23e606c
feat: add GriddedColorKey decorator for MTEX-style legend rendering
imikejackson Apr 3, 2026
3874e01
chore: generate gridded NH legends at 2000x2000 with 0.5-degree grid
imikejackson Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion Source/Apps/generate_ipf_legends.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include "EbsdLib/Utilities/CanvasUtilities.hpp"
#include "EbsdLib/Utilities/ColorTable.h"
#include "EbsdLib/Utilities/EbsdStringUtils.hpp"
#include "EbsdLib/Utilities/FundamentalSectorGeometry.hpp"
#include "EbsdLib/Utilities/GriddedColorKey.hpp"
#include "EbsdLib/Utilities/NolzeHielscherColorKey.hpp"
#include "EbsdLib/Utilities/TSLColorKey.hpp"
#include "EbsdLib/Utilities/TiffWriter.h"

#include "EbsdLib/Apps/EbsdLibFileLocations.h"
Expand All @@ -25,6 +29,7 @@
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <sstream>
Expand Down Expand Up @@ -324,6 +329,75 @@ void GeneratePoleFigures(LaueOps& ops, int symType)
}
}

// -----------------------------------------------------------------------------
void GenerateNolzeHielscherLegends(int imageDim)
{
std::cout << "\n=== Generating Nolze-Hielscher IPF Legends ===\n" << std::endl;

auto allOps = LaueOps::GetAllOrientationOps();

// Map from LaueOps index to FundamentalSectorGeometry factory
std::vector<std::function<ebsdlib::FundamentalSectorGeometry()>> sectorFactories = {
ebsdlib::FundamentalSectorGeometry::hexagonalHigh, // 0: Hexagonal_High
ebsdlib::FundamentalSectorGeometry::cubicHigh, // 1: Cubic_High
ebsdlib::FundamentalSectorGeometry::hexagonalLow, // 2: Hexagonal_Low
ebsdlib::FundamentalSectorGeometry::cubicLow, // 3: Cubic_Low
ebsdlib::FundamentalSectorGeometry::triclinic, // 4: Triclinic
ebsdlib::FundamentalSectorGeometry::monoclinic, // 5: Monoclinic
ebsdlib::FundamentalSectorGeometry::orthorhombic, // 6: OrthoRhombic
ebsdlib::FundamentalSectorGeometry::tetragonalLow, // 7: Tetragonal_Low
ebsdlib::FundamentalSectorGeometry::tetragonalHigh, // 8: Tetragonal_High
ebsdlib::FundamentalSectorGeometry::trigonalLow, // 9: Trigonal_Low
ebsdlib::FundamentalSectorGeometry::trigonalHigh, // 10: Trigonal_High
};

for(size_t i = 0; i < allOps.size(); i++)
{
auto& ops = *allOps[i];
std::string symName = EbsdStringUtils::replace(ops.getSymmetryName(), "/", "_");

// Set NH color key
auto sector = sectorFactories[i]();
auto nhKey = std::make_shared<ebsdlib::NolzeHielscherColorKey>(sector);
ops.setColorKey(nhKey);

// Generate full-circle legend
auto legend = ops.generateIPFTriangleLegend(imageDim, true);
std::stringstream ss;
ss << k_Output_Dir << "/" << symName << "/" << symName << "_NH_FULL.tiff";
auto result = TiffWriter::WriteColorImage(ss.str(), imageDim, imageDim, 3, legend->getPointer(0));
std::cout << ops.getSymmetryName() << " NH Full Result: " << result.first << ": " << result.second << std::endl;

// Generate triangle-only legend
legend = ops.generateIPFTriangleLegend(imageDim, false);
ss.str("");
ss << k_Output_Dir << "/" << symName << "/" << symName << "_NH.tiff";
result = TiffWriter::WriteColorImage(ss.str(), imageDim, imageDim, 3, legend->getPointer(0));
std::cout << ops.getSymmetryName() << " NH Triangle Result: " << result.first << ": " << result.second << std::endl;

// Set to grid-interpolated mode (MTEX-style rendering, 0.5 degree grid)
ops.setLegendRenderMode(ebsdlib::LegendRenderMode::GridInterpolated, 0.5);

// Generate gridded legends at higher resolution (2000x2000)
constexpr int k_GriddedImageDim = 2000;
legend = ops.generateIPFTriangleLegend(k_GriddedImageDim, true);
ss.str("");
ss << k_Output_Dir << "/" << symName << "/" << symName << "_NH_GRIDDED_FULL.tiff";
result = TiffWriter::WriteColorImage(ss.str(), k_GriddedImageDim, k_GriddedImageDim, 3, legend->getPointer(0));
std::cout << ops.getSymmetryName() << " NH Gridded Full Result: " << result.first << ": " << result.second << std::endl;

// Generate gridded triangle-only legend
legend = ops.generateIPFTriangleLegend(k_GriddedImageDim, false);
ss.str("");
ss << k_Output_Dir << "/" << symName << "/" << symName << "_NH_GRIDDED.tiff";
result = TiffWriter::WriteColorImage(ss.str(), k_GriddedImageDim, k_GriddedImageDim, 3, legend->getPointer(0));
std::cout << ops.getSymmetryName() << " NH Gridded Triangle Result: " << result.first << ": " << result.second << std::endl;

// Reset to TSL for subsequent operations
ops.setColorKey(std::make_shared<ebsdlib::TSLColorKey>());
}
}

// -----------------------------------------------------------------------------
int main(int argc, char* argv[])
{
Expand All @@ -338,7 +412,7 @@ int main(int argc, char* argv[])
}

std::stringstream ss;
int imageDim = 512;
int imageDim = 1500;
{
TrigonalOps ops;
auto legend = ops.generateIPFTriangleLegend(imageDim, true);
Expand Down Expand Up @@ -697,5 +771,7 @@ int main(int argc, char* argv[])
GeneratePoleFigures(ops, 1);
}

GenerateNolzeHielscherLegends(imageDim);

return 0;
}
54 changes: 53 additions & 1 deletion Source/EbsdLib/LaueOps/LaueOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
#include "EbsdLib/Orientation/Quaternion.hpp"
#include "EbsdLib/Utilities/ColorTable.h"
#include "EbsdLib/Utilities/ComputeStereographicProjection.h"
#include "EbsdLib/Utilities/GriddedColorKey.hpp"
#include "EbsdLib/Utilities/TSLColorKey.hpp"

#include <algorithm> // for std::max
#include <chrono>
Expand Down Expand Up @@ -92,11 +94,52 @@ constexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept
} // namespace

// -----------------------------------------------------------------------------
LaueOps::LaueOps() = default;
LaueOps::LaueOps()
: m_ColorKey(std::make_shared<ebsdlib::TSLColorKey>())
{
}

// -----------------------------------------------------------------------------
LaueOps::~LaueOps() = default;

// -----------------------------------------------------------------------------
void LaueOps::setColorKey(ebsdlib::IColorKey::Pointer colorKey)
{
m_ColorKey = colorKey;
}

// -----------------------------------------------------------------------------
ebsdlib::IColorKey::Pointer LaueOps::getColorKey() const
{
return m_ColorKey;
}

// -----------------------------------------------------------------------------
void LaueOps::setLegendRenderMode(ebsdlib::LegendRenderMode mode, double gridResolutionDeg)
{
if(mode == ebsdlib::LegendRenderMode::GridInterpolated)
{
// Wrap the current color key with a GriddedColorKey if not already wrapped
auto currentKey = m_ColorKey;
// If already gridded, unwrap first to avoid double-wrapping
auto griddedKey = std::dynamic_pointer_cast<ebsdlib::GriddedColorKey>(currentKey);
if(griddedKey)
{
currentKey = griddedKey->innerKey();
}
m_ColorKey = std::make_shared<ebsdlib::GriddedColorKey>(currentKey, gridResolutionDeg);
}
else
{
// PerPixel mode: unwrap if currently gridded
auto griddedKey = std::dynamic_pointer_cast<ebsdlib::GriddedColorKey>(m_ColorKey);
if(griddedKey)
{
m_ColorKey = griddedKey->innerKey();
}
}
}

// -----------------------------------------------------------------------------
std::string LaueOps::FZTypeToString(const FZType value)
{
Expand Down Expand Up @@ -201,6 +244,15 @@ ebsdlib::Rgb LaueOps::computeIPFColor(double* eulers, double* refDir, bool degTo

const std::array<double, 3> angleLimits = getIpfColorAngleLimits(eta);

if(m_ColorKey)
{
auto [r, g, b] = m_ColorKey->direction2Color(eta, chi, angleLimits);
_rgb[0] = r;
_rgb[1] = g;
_rgb[2] = b;
return ebsdlib::RgbColor::dRgb(static_cast<int32_t>(_rgb[0] * 255), static_cast<int32_t>(_rgb[1] * 255), static_cast<int32_t>(_rgb[2] * 255), 255);
}

_rgb[0] = 1.0 - chi / angleLimits[2];
_rgb[2] = std::fabs(eta - angleLimits[0]) / (angleLimits[1] - angleLimits[0]);
_rgb[1] = 1 - _rgb[2];
Expand Down
26 changes: 26 additions & 0 deletions Source/EbsdLib/LaueOps/LaueOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
#include "EbsdLib/Orientation/OrientationFwd.hpp"
#include "EbsdLib/Orientation/Quaternion.hpp"
#include "EbsdLib/Orientation/Rodrigues.hpp"
#include "EbsdLib/Utilities/GriddedColorKey.hpp"
#include "EbsdLib/Utilities/IColorKey.hpp"
#include "EbsdLib/Utilities/PoleFigureUtilities.h"
#include "EbsdLib/Utilities/TSLColorKey.hpp"

namespace ebsdlib
{
Expand Down Expand Up @@ -288,6 +291,27 @@ class EbsdLib_EXPORT LaueOps
*/
virtual Rgb generateIPFColor(double e0, double e1, double e2, double dir0, double dir1, double dir2, bool convertDegrees) const = 0;

/**
* @brief Sets the color key strategy used for IPF coloring.
* @param colorKey The color key to use
*/
void setColorKey(ebsdlib::IColorKey::Pointer colorKey);

/**
* @brief Returns the current color key strategy used for IPF coloring.
* @return The current color key
*/
ebsdlib::IColorKey::Pointer getColorKey() const;

/**
* @brief Set the legend rendering mode.
* PerPixel: exact color at every pixel (default, current behavior)
* GridInterpolated: MTEX-style flat-shaded grid cells at the given resolution
* @param mode The rendering mode to use
* @param gridResolutionDeg Grid cell size in degrees (only used for GridInterpolated mode)
*/
void setLegendRenderMode(ebsdlib::LegendRenderMode mode, double gridResolutionDeg = 1.0);

/**
* @brief generateRodriguesColor Generates an RGB Color from a Rodrigues Vector
* @param r1 First component of the Rodrigues Vector
Expand Down Expand Up @@ -507,6 +531,8 @@ class EbsdLib_EXPORT LaueOps
*/
Rgb computeIPFColor(double* eulers, double* refDir, bool degToRad) const;

ebsdlib::IColorKey::Pointer m_ColorKey;

/**
* @brief Converts in input Quaternion into a version that is inside the fundamental zone.
*
Expand Down
94 changes: 94 additions & 0 deletions Source/EbsdLib/Utilities/ColorSpaceUtils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#pragma once

#include "EbsdLib/EbsdLib.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>

namespace ebsdlib
{
namespace color
{

/**
* @brief Convert HSL to RGB. All inputs and outputs in [0, 1].
* @param h Hue in [0, 1) where 0=red, 1/3=green, 2/3=blue
* @param s Saturation in [0, 1]
* @param l Lightness in [0, 1]
* @return {r, g, b} each in [0, 1]
*/
inline std::array<double, 3> hslToRgb(double h, double s, double l)
{
double c = (1.0 - std::abs(2.0 * l - 1.0)) * s;
double hp = h * 6.0;
double x = c * (1.0 - std::abs(std::fmod(hp, 2.0) - 1.0));
double m = l - c / 2.0;

double r1 = 0.0;
double g1 = 0.0;
double b1 = 0.0;

if(hp < 1.0)
{
r1 = c;
g1 = x;
b1 = 0.0;
}
else if(hp < 2.0)
{
r1 = x;
g1 = c;
b1 = 0.0;
}
else if(hp < 3.0)
{
r1 = 0.0;
g1 = c;
b1 = x;
}
else if(hp < 4.0)
{
r1 = 0.0;
g1 = x;
b1 = c;
}
else if(hp < 5.0)
{
r1 = x;
g1 = 0.0;
b1 = c;
}
else
{
r1 = c;
g1 = 0.0;
b1 = x;
}

return {std::clamp(r1 + m, 0.0, 1.0), std::clamp(g1 + m, 0.0, 1.0), std::clamp(b1 + m, 0.0, 1.0)};
}

/**
* @brief Convert HSL to HSV. All inputs and outputs in [0, 1].
*/
inline std::array<double, 3> hslToHsv(double h, double s, double l)
{
double l2 = 2.0 * l;
double s2 = s * ((l2 <= 1.0) ? l2 : (2.0 - l2));
double v = (l2 + s2) / 2.0;
double sv = (l2 + s2 > 1e-12) ? (2.0 * s2 / (l2 + s2)) : 0.0;
return {h, sv, v};
}

/**
* @brief Convert RGB [0,1] to 8-bit [0,255] clamped.
*/
inline std::array<uint8_t, 3> rgbToBytes(double r, double g, double b)
{
return {static_cast<uint8_t>(std::clamp(r * 255.0, 0.0, 255.0)), static_cast<uint8_t>(std::clamp(g * 255.0, 0.0, 255.0)), static_cast<uint8_t>(std::clamp(b * 255.0, 0.0, 255.0))};
}

} // namespace color
} // namespace ebsdlib
Loading
Loading