Skip to content

Commit 04ccda9

Browse files
imikejacksonclaude
andcommitted
ENH: Add stereographic SST mapping for properly shaped IPF density images
Add virtual mapPixelToSphereSST() method to LaueOps with per-subclass overrides that match the exact stereographic projection used by each CreateIPFLegend function. Update computeIPFIntensity with an optional useStereographicSST parameter so IPF density images use the same projection as the IPF legend, ensuring triangle shapes match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 37b09b8 commit 04ccda9

26 files changed

Lines changed: 598 additions & 363 deletions

Source/EbsdLib/LaueOps/CubicLowOps.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -992,11 +992,36 @@ ebsdlib::UInt8ArrayType::Pointer CreateIPFLegend(const CubicLowOps* ops, int ima
992992
} // namespace
993993

994994
// -----------------------------------------------------------------------------
995-
std::array<float, 2> CubicLowOps::adjustFigureOrigin(
996-
std::array<float, 2> figureOrigin,
997-
int legendWidth, int legendHeight,
998-
const std::vector<float>& margins, float fontPtSize,
999-
bool generateEntirePlane) const
995+
bool CubicLowOps::mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const
996+
{
997+
double xInc = 1.0 / static_cast<double>(imageDim);
998+
double yInc = 1.0 / static_cast<double>(imageDim);
999+
1000+
double x = 0.5 * static_cast<double>(xPixel) * xInc;
1001+
double y = 0.5 * static_cast<double>(yPixel) * yInc;
1002+
1003+
double sumSquares = (x * x) + (y * y);
1004+
if(sumSquares > 1.0)
1005+
{
1006+
return false;
1007+
}
1008+
1009+
auto sc = stereographic::utils::StereoToSpherical(x, y).normalize();
1010+
1011+
if(!(sc[2] > sc[0] && sc[2] > sc[1]))
1012+
{
1013+
return false;
1014+
}
1015+
1016+
sphereDir[0] = static_cast<float>(sc[0]);
1017+
sphereDir[1] = static_cast<float>(sc[1]);
1018+
sphereDir[2] = static_cast<float>(sc[2]);
1019+
return true;
1020+
}
1021+
1022+
// -----------------------------------------------------------------------------
1023+
std::array<float, 2> CubicLowOps::adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
1024+
bool generateEntirePlane) const
10001025
{
10011026
if(!generateEntirePlane)
10021027
{
@@ -1152,12 +1177,7 @@ ebsdlib::UInt8ArrayType::Pointer CubicLowOps::generateIPFTriangleLegend(int canv
11521177
{
11531178
// Compute legend dimensions (same formula as annotateIPFImage uses)
11541179
const float fontPtSize = static_cast<float>(canvasDim) / 24.0f;
1155-
const std::vector<float> margins = {
1156-
fontPtSize * 3,
1157-
static_cast<float>(canvasDim / 7.0f),
1158-
fontPtSize * 2,
1159-
static_cast<float>(canvasDim / 7.0f)
1160-
};
1180+
const std::vector<float> margins = {fontPtSize * 3, static_cast<float>(canvasDim / 7.0f), fontPtSize * 2, static_cast<float>(canvasDim / 7.0f)};
11611181
int legendHeight = canvasDim - static_cast<int>(margins[0]) - static_cast<int>(margins[2]);
11621182
int legendWidth = canvasDim - static_cast<int>(margins[1]) - static_cast<int>(margins[3]);
11631183
if(legendHeight > legendWidth)

Source/EbsdLib/LaueOps/CubicLowOps.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -260,17 +260,13 @@ class EbsdLib_EXPORT CubicLowOps : public LaueOps
260260
*/
261261
ebsdlib::UInt8ArrayType::Pointer generateIPFTriangleLegend(int imageDim, bool generateEntirePlane) const override;
262262

263-
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim,
264-
float fontPtSize, const std::vector<float>& margins,
265-
std::array<float, 2> figureOrigin,
266-
std::array<float, 2> figureCenter,
267-
bool drawFullCircle) const override;
268-
269-
std::array<float, 2> adjustFigureOrigin(
270-
std::array<float, 2> figureOrigin,
271-
int legendWidth, int legendHeight,
272-
const std::vector<float>& margins, float fontPtSize,
273-
bool generateEntirePlane) const override;
263+
bool mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const override;
264+
265+
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim, float fontPtSize, const std::vector<float>& margins, std::array<float, 2> figureOrigin, std::array<float, 2> figureCenter,
266+
bool drawFullCircle) const override;
267+
268+
std::array<float, 2> adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
269+
bool generateEntirePlane) const override;
274270

275271
/**
276272
* @brief Returns if the given Quaternion is within the Rodrigues Fundamental Zone (RFZ)

Source/EbsdLib/LaueOps/CubicOps.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,11 +1955,47 @@ ebsdlib::UInt8ArrayType::Pointer CreateIPFLegend(const CubicOps* ops, int imageD
19551955
} // namespace
19561956

19571957
// -----------------------------------------------------------------------------
1958-
std::array<float, 2> CubicOps::adjustFigureOrigin(
1959-
std::array<float, 2> figureOrigin,
1960-
int legendWidth, int legendHeight,
1961-
const std::vector<float>& margins, float fontPtSize,
1962-
bool generateEntirePlane) const
1958+
bool CubicOps::mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const
1959+
{
1960+
double indexConst1 = 0.414 / static_cast<double>(imageDim);
1961+
double indexConst2 = 0.207 / static_cast<double>(imageDim);
1962+
1963+
double x = xPixel * indexConst1 + indexConst2;
1964+
double y = yPixel * indexConst1 + indexConst2;
1965+
1966+
double sumSquares = (x * x) + (y * y);
1967+
if(sumSquares > 1.0)
1968+
{
1969+
return false;
1970+
}
1971+
if(y < 0.0 || x < 0.0)
1972+
{
1973+
return false;
1974+
}
1975+
1976+
auto sc = stereographic::utils::StereoToSpherical(x, y).normalize();
1977+
1978+
double k_RootOfHalf = std::sqrt(0.5);
1979+
double red1 = sc[0] * (-k_RootOfHalf) + sc[2] * k_RootOfHalf;
1980+
double phi = std::acos(red1);
1981+
double x1alt = sc[0] / k_RootOfHalf;
1982+
x1alt = x1alt / std::sqrt((x1alt * x1alt) + (sc[1] * sc[1]));
1983+
double theta = std::acos(x1alt);
1984+
1985+
if(phi <= (45.0 * ebsdlib::constants::k_PiOver180D) || phi >= (90.0 * ebsdlib::constants::k_PiOver180D) || theta >= (35.26 * ebsdlib::constants::k_PiOver180D))
1986+
{
1987+
return false;
1988+
}
1989+
1990+
sphereDir[0] = static_cast<float>(sc[0]);
1991+
sphereDir[1] = static_cast<float>(sc[1]);
1992+
sphereDir[2] = static_cast<float>(sc[2]);
1993+
return true;
1994+
}
1995+
1996+
// -----------------------------------------------------------------------------
1997+
std::array<float, 2> CubicOps::adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
1998+
bool generateEntirePlane) const
19631999
{
19642000
if(!generateEntirePlane)
19652001
{
@@ -2104,12 +2140,7 @@ ebsdlib::UInt8ArrayType::Pointer CubicOps::generateIPFTriangleLegend(int canvasD
21042140
{
21052141
// Compute legend dimensions (same formula as annotateIPFImage uses)
21062142
const float fontPtSize = static_cast<float>(canvasDim) / 24.0f;
2107-
const std::vector<float> margins = {
2108-
fontPtSize * 3,
2109-
static_cast<float>(canvasDim / 7.0f),
2110-
fontPtSize * 2,
2111-
static_cast<float>(canvasDim / 7.0f)
2112-
};
2143+
const std::vector<float> margins = {fontPtSize * 3, static_cast<float>(canvasDim / 7.0f), fontPtSize * 2, static_cast<float>(canvasDim / 7.0f)};
21132144
int legendHeight = canvasDim - static_cast<int>(margins[0]) - static_cast<int>(margins[2]);
21142145
int legendWidth = canvasDim - static_cast<int>(margins[1]) - static_cast<int>(margins[3]);
21152146
if(legendHeight > legendWidth)

Source/EbsdLib/LaueOps/CubicOps.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -306,17 +306,13 @@ class EbsdLib_EXPORT CubicOps : public LaueOps
306306
*/
307307
ebsdlib::UInt8ArrayType::Pointer generateIPFTriangleLegend(int imageDim, bool generateEntirePlane) const override;
308308

309-
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim,
310-
float fontPtSize, const std::vector<float>& margins,
311-
std::array<float, 2> figureOrigin,
312-
std::array<float, 2> figureCenter,
313-
bool drawFullCircle) const override;
314-
315-
std::array<float, 2> adjustFigureOrigin(
316-
std::array<float, 2> figureOrigin,
317-
int legendWidth, int legendHeight,
318-
const std::vector<float>& margins, float fontPtSize,
319-
bool generateEntirePlane) const override;
309+
bool mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const override;
310+
311+
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim, float fontPtSize, const std::vector<float>& margins, std::array<float, 2> figureOrigin, std::array<float, 2> figureCenter,
312+
bool drawFullCircle) const override;
313+
314+
std::array<float, 2> adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
315+
bool generateEntirePlane) const override;
320316

321317
/**
322318
* @brief Returns if the given Quaternion is within the Rodrigues Fundamental Zone (RFZ)

Source/EbsdLib/LaueOps/HexagonalLowOps.cpp

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,11 +1437,44 @@ ebsdlib::UInt8ArrayType::Pointer CreateIPFLegend(const HexagonalLowOps* ops, int
14371437
} // namespace
14381438

14391439
// -----------------------------------------------------------------------------
1440-
std::array<float, 2> HexagonalLowOps::adjustFigureOrigin(
1441-
std::array<float, 2> figureOrigin,
1442-
int legendWidth, int legendHeight,
1443-
const std::vector<float>& margins, float fontPtSize,
1444-
bool generateEntirePlane) const
1440+
bool HexagonalLowOps::mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const
1441+
{
1442+
double xInc = 1.0 / static_cast<double>(imageDim);
1443+
double yInc = 1.0 / static_cast<double>(imageDim);
1444+
1445+
double x = -1.0 + 2.0 * xPixel * xInc;
1446+
double y = -1.0 + 2.0 * yPixel * yInc;
1447+
1448+
double sumSquares = (x * x) + (y * y);
1449+
if(sumSquares > 1.0)
1450+
{
1451+
return false;
1452+
}
1453+
1454+
if(x < 0.0 || y < 0.0)
1455+
{
1456+
return false;
1457+
}
1458+
1459+
// Find the slope of the bounding line.
1460+
static const double m = std::sin(60.0 * ebsdlib::constants::k_PiOver180D) / std::cos(60.0 * ebsdlib::constants::k_PiOver180D);
1461+
1462+
if(x < y / m)
1463+
{
1464+
return false;
1465+
}
1466+
1467+
auto sc = stereographic::utils::StereoToSpherical(x, y).normalize();
1468+
1469+
sphereDir[0] = static_cast<float>(sc[0]);
1470+
sphereDir[1] = static_cast<float>(sc[1]);
1471+
sphereDir[2] = static_cast<float>(sc[2]);
1472+
return true;
1473+
}
1474+
1475+
// -----------------------------------------------------------------------------
1476+
std::array<float, 2> HexagonalLowOps::adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
1477+
bool generateEntirePlane) const
14451478
{
14461479
if(!generateEntirePlane)
14471480
{
@@ -1553,12 +1586,7 @@ ebsdlib::UInt8ArrayType::Pointer HexagonalLowOps::generateIPFTriangleLegend(int
15531586
{
15541587
// Compute legend dimensions (same formula as annotateIPFImage uses)
15551588
const float fontPtSize = static_cast<float>(canvasDim) / 24.0f;
1556-
const std::vector<float> margins = {
1557-
fontPtSize * 3,
1558-
static_cast<float>(canvasDim / 7.0f),
1559-
fontPtSize * 2,
1560-
static_cast<float>(canvasDim / 7.0f)
1561-
};
1589+
const std::vector<float> margins = {fontPtSize * 3, static_cast<float>(canvasDim / 7.0f), fontPtSize * 2, static_cast<float>(canvasDim / 7.0f)};
15621590
int legendHeight = canvasDim - static_cast<int>(margins[0]) - static_cast<int>(margins[2]);
15631591
int legendWidth = canvasDim - static_cast<int>(margins[1]) - static_cast<int>(margins[3]);
15641592
if(legendHeight > legendWidth)

Source/EbsdLib/LaueOps/HexagonalLowOps.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -260,17 +260,13 @@ class EbsdLib_EXPORT HexagonalLowOps : public LaueOps
260260
*/
261261
ebsdlib::UInt8ArrayType::Pointer generateIPFTriangleLegend(int imageDim, bool generateEntirePlane) const override;
262262

263-
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim,
264-
float fontPtSize, const std::vector<float>& margins,
265-
std::array<float, 2> figureOrigin,
266-
std::array<float, 2> figureCenter,
267-
bool drawFullCircle) const override;
268-
269-
std::array<float, 2> adjustFigureOrigin(
270-
std::array<float, 2> figureOrigin,
271-
int legendWidth, int legendHeight,
272-
const std::vector<float>& margins, float fontPtSize,
273-
bool generateEntirePlane) const override;
263+
bool mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const override;
264+
265+
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim, float fontPtSize, const std::vector<float>& margins, std::array<float, 2> figureOrigin, std::array<float, 2> figureCenter,
266+
bool drawFullCircle) const override;
267+
268+
std::array<float, 2> adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
269+
bool generateEntirePlane) const override;
274270

275271
/**
276272
* @brief Returns if the given Quaternion is within the Rodrigues Fundamental Zone (RFZ)

Source/EbsdLib/LaueOps/HexagonalOps.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,11 +1491,47 @@ ebsdlib::UInt8ArrayType::Pointer CreateIPFLegend(const HexagonalOps* ops, int im
14911491
} // namespace
14921492

14931493
// -----------------------------------------------------------------------------
1494-
std::array<float, 2> HexagonalOps::adjustFigureOrigin(
1495-
std::array<float, 2> figureOrigin,
1496-
int legendWidth, int legendHeight,
1497-
const std::vector<float>& margins, float fontPtSize,
1498-
bool generateEntirePlane) const
1494+
bool HexagonalOps::mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const
1495+
{
1496+
double xInc = 1.0 / static_cast<double>(imageDim);
1497+
double yInc = 1.0 / static_cast<double>(imageDim);
1498+
1499+
double x = -1.0 + 2.0 * xPixel * xInc;
1500+
double y = -1.0 + 2.0 * yPixel * yInc;
1501+
1502+
double sumSquares = (x * x) + (y * y);
1503+
if(sumSquares > 1.0)
1504+
{
1505+
return false;
1506+
}
1507+
1508+
// Find the slope of the bounding line.
1509+
static const double m = -1.0 * std::sin(30.0 * ebsdlib::constants::k_PiOver180D) / std::cos(30.0 * ebsdlib::constants::k_PiOver180D);
1510+
1511+
if(x < y / m && x > 0.0)
1512+
{
1513+
return false;
1514+
}
1515+
if(x > y / m && y > 0.0)
1516+
{
1517+
return false;
1518+
}
1519+
if(x < 0.0)
1520+
{
1521+
return false;
1522+
}
1523+
1524+
auto sc = stereographic::utils::StereoToSpherical(x, y).normalize();
1525+
1526+
sphereDir[0] = static_cast<float>(sc[0]);
1527+
sphereDir[1] = static_cast<float>(sc[1]);
1528+
sphereDir[2] = static_cast<float>(sc[2]);
1529+
return true;
1530+
}
1531+
1532+
// -----------------------------------------------------------------------------
1533+
std::array<float, 2> HexagonalOps::adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
1534+
bool generateEntirePlane) const
14991535
{
15001536
if(!generateEntirePlane)
15011537
{
@@ -1597,12 +1633,7 @@ ebsdlib::UInt8ArrayType::Pointer HexagonalOps::generateIPFTriangleLegend(int can
15971633
{
15981634
// Compute legend dimensions (same formula as annotateIPFImage uses)
15991635
const float fontPtSize = static_cast<float>(canvasDim) / 24.0f;
1600-
const std::vector<float> margins = {
1601-
fontPtSize * 3,
1602-
static_cast<float>(canvasDim / 7.0f),
1603-
fontPtSize * 2,
1604-
static_cast<float>(canvasDim / 7.0f)
1605-
};
1636+
const std::vector<float> margins = {fontPtSize * 3, static_cast<float>(canvasDim / 7.0f), fontPtSize * 2, static_cast<float>(canvasDim / 7.0f)};
16061637
int legendHeight = canvasDim - static_cast<int>(margins[0]) - static_cast<int>(margins[2]);
16071638
int legendWidth = canvasDim - static_cast<int>(margins[1]) - static_cast<int>(margins[3]);
16081639
if(legendHeight > legendWidth)

Source/EbsdLib/LaueOps/HexagonalOps.h

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -260,17 +260,13 @@ class EbsdLib_EXPORT HexagonalOps : public LaueOps
260260
*/
261261
ebsdlib::UInt8ArrayType::Pointer generateIPFTriangleLegend(int imageDim, bool generateEntirePlane) const override;
262262

263-
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim,
264-
float fontPtSize, const std::vector<float>& margins,
265-
std::array<float, 2> figureOrigin,
266-
std::array<float, 2> figureCenter,
267-
bool drawFullCircle) const override;
268-
269-
std::array<float, 2> adjustFigureOrigin(
270-
std::array<float, 2> figureOrigin,
271-
int legendWidth, int legendHeight,
272-
const std::vector<float>& margins, float fontPtSize,
273-
bool generateEntirePlane) const override;
263+
bool mapPixelToSphereSST(int xPixel, int yPixel, int imageDim, std::array<float, 3>& sphereDir) const override;
264+
265+
void drawIPFAnnotations(canvas_ity::canvas& context, int canvasDim, float fontPtSize, const std::vector<float>& margins, std::array<float, 2> figureOrigin, std::array<float, 2> figureCenter,
266+
bool drawFullCircle) const override;
267+
268+
std::array<float, 2> adjustFigureOrigin(std::array<float, 2> figureOrigin, int legendWidth, int legendHeight, const std::vector<float>& margins, float fontPtSize,
269+
bool generateEntirePlane) const override;
274270

275271
/**
276272
* @brief Returns if the given Quaternion is within the Rodrigues Fundamental Zone (RFZ)

0 commit comments

Comments
 (0)