Skip to content

Commit

Permalink
ENH: Add unambiguous strong conversion for orientations
Browse files Browse the repository at this point in the history
  • Loading branch information
blowekamp committed Sep 3, 2024
1 parent f737800 commit 7192104
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 111 deletions.
35 changes: 25 additions & 10 deletions Modules/Core/Common/include/itkAnatomicalOrientation.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class ITKCommon_EXPORT AnatomicalOrientation

{
INVALID = 0,

RIP = m_OrientationValue<CoordinateEnum::LeftToRight,
CoordinateEnum::SuperiorToInferior,
CoordinateEnum::AnteriorToPosterior>,
Expand Down Expand Up @@ -247,6 +248,7 @@ class ITKCommon_EXPORT AnatomicalOrientation

{
INVALID = 0,

RIP = m_OrientationValue<CoordinateEnum::RightToLeft,
CoordinateEnum::InferiorToSuperior,
CoordinateEnum::PosteriorToAnterior>,
Expand Down Expand Up @@ -416,11 +418,10 @@ class ITKCommon_EXPORT AnatomicalOrientation
*
* @param legacyOrientation
*/
AnatomicalOrientation(LegacyOrientationType legacyOrientation)
# ifdef ITK_FUTURE_LEGACY_REMOVE
[deprecated("Use the AnatomicalOrientation::FromEnum type instead.")]
# ifdef ITK_LEGACY_REMOVE
[[deprecated("Use the AnatomicalOrientation::FromEnum type instead.")]]
# endif
;
AnatomicalOrientation(LegacyOrientationType legacyOrientation);
#endif

AnatomicalOrientation(FromEnum orientation);
Expand All @@ -429,12 +430,28 @@ class ITKCommon_EXPORT AnatomicalOrientation
: m_Value(DirectionCosinesToOrientation(d))
{}

explicit AnatomicalOrientation(std::string str);

operator ToEnum() const { return m_Value; }

std::string
GetAsString() const;
GetAsToStringEncoding() const;

std::string
GetAsFromStringEncoding() const;

static AnatomicalOrientation
CreateFromToStringEncoding(std::string str);

static AnatomicalOrientation
CreateFromFromStringEncoding(std::string str);

/** An involution to convert between "to" and "from" single charactor encoding strings.
*
* For example the string "RAS" is converted to "LPI" and vice versa.
*
*
* */
static std::string
ConvertStringEncoding(std::string str);

DirectionType
GetAsDirection() const
Expand Down Expand Up @@ -475,7 +492,7 @@ class ITKCommon_EXPORT AnatomicalOrientation
static bool
SameOrientationAxes(CoordinateEnum a, CoordinateEnum b)
{
const unsigned int AxisField = 0xE; // b1110, mask lowest bit
const unsigned int AxisField = 0xE; // b1110, mask the lowest bit
return (static_cast<uint8_t>(a) & AxisField) == (static_cast<uint8_t>(b) & AxisField);
}

Expand All @@ -493,8 +510,6 @@ class ITKCommon_EXPORT AnatomicalOrientation

private:
// Private methods to create the maps, these will only be called once.
static std::map<ToEnum, std::string>
CreateCodeToString();

/** \brief Return the global instance of the map from orientation enum to strings.
*
Expand Down
154 changes: 70 additions & 84 deletions Modules/Core/Common/src/itkAnatomicalOrientation.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ AnatomicalOrientation::AnatomicalOrientation(CoordinateEnum primary, CoordinateE
}
}

AnatomicalOrientation::AnatomicalOrientation(std::string str)
: m_Value(ToEnum::INVALID)
{
std::transform(str.begin(), str.end(), str.begin(), ::toupper);

const std::map<std::string, typename AnatomicalOrientation::ToEnum> & stringToCode = GetStringToCode();
auto iter = stringToCode.find(str);
if (iter != stringToCode.end())
{
m_Value = iter->second;
}
}


#ifndef ITK_FUTURE_LEGACY_REMOVE
AnatomicalOrientation::AnatomicalOrientation(LegacyOrientationType legacyOrientation)
: AnatomicalOrientation(SpatialOrientationAdapter::ToDirectionCosines(legacyOrientation))
Expand All @@ -68,7 +54,7 @@ AnatomicalOrientation::AnatomicalOrientation(AnatomicalOrientation::FromEnum fro


std::string
AnatomicalOrientation::GetAsString() const
AnatomicalOrientation::GetAsToStringEncoding() const
{

// a lambda function to convert a CoordinateEnum to a char
Expand Down Expand Up @@ -101,6 +87,52 @@ AnatomicalOrientation::GetAsString() const
}


std::string
AnatomicalOrientation::GetAsFromStringEncoding() const
{
return ConvertStringEncoding(GetAsToStringEncoding());
}


AnatomicalOrientation
AnatomicalOrientation::CreateFromToStringEncoding(std::string str)
{
std::transform(str.begin(), str.end(), str.begin(), ::toupper);

const std::map<std::string, typename AnatomicalOrientation::ToEnum> & stringToCode = GetStringToCode();
auto iter = stringToCode.find(str);
if (iter == stringToCode.end())
{
return AnatomicalOrientation(ToEnum::INVALID);
}
return AnatomicalOrientation(iter->second);
}

AnatomicalOrientation
AnatomicalOrientation::CreateFromFromStringEncoding(std::string str)
{
return AnatomicalOrientation::CreateFromToStringEncoding(ConvertStringEncoding(str));
}


std::string
AnatomicalOrientation::ConvertStringEncoding(std::string str)
{
const std::map<char, char> flipmap = { { 'R', 'L' }, { 'L', 'R' }, { 'A', 'P' }, { 'P', 'A' },
{ 'S', 'I' }, { 'I', 'S' }, { 'X', 'X' } };

for (auto & c : str)
{
auto it = flipmap.find(toupper(c));
if (it != flipmap.end())
{
c = it->second;
}
}
return str;
}


AnatomicalOrientation::CoordinateEnum
AnatomicalOrientation::GetCoordinateTerm(CoordinateMajornessTermsEnum cmt) const
{
Expand All @@ -111,7 +143,23 @@ AnatomicalOrientation::GetCoordinateTerm(CoordinateMajornessTermsEnum cmt) const
const std::map<typename AnatomicalOrientation::ToEnum, std::string> &
AnatomicalOrientation::GetCodeToString()
{
static const std::map<ToEnum, std::string> codeToString = CreateCodeToString();
auto createCodeToString = []() -> std::map<ToEnum, std::string> {
std::map<ToEnum, std::string> orientToString;

for (auto code : { ToEnum::RIP, ToEnum::LIP, ToEnum::RSP, ToEnum::LSP, ToEnum::RIA, ToEnum::LIA, ToEnum::RSA,
ToEnum::LSA, ToEnum::IRP, ToEnum::ILP, ToEnum::SRP, ToEnum::SLP, ToEnum::IRA, ToEnum::ILA,
ToEnum::SRA, ToEnum::SLA, ToEnum::RPI, ToEnum::LPI, ToEnum::RAI, ToEnum::LAI, ToEnum::RPS,
ToEnum::LPS, ToEnum::RAS, ToEnum::LAS, ToEnum::PRI, ToEnum::PLI, ToEnum::ARI, ToEnum::ALI,
ToEnum::PRS, ToEnum::PLS, ToEnum::ARS, ToEnum::ALS, ToEnum::IPR, ToEnum::SPR, ToEnum::IAR,
ToEnum::SAR, ToEnum::IPL, ToEnum::SPL, ToEnum::IAL, ToEnum::SAL, ToEnum::PIR, ToEnum::PSR,
ToEnum::AIR, ToEnum::ASR, ToEnum::PIL, ToEnum::PSL, ToEnum::AIL, ToEnum::ASL, ToEnum::INVALID })
{
orientToString[code] = AnatomicalOrientation(code).GetAsToStringEncoding();
}

return orientToString;
};
static const std::map<ToEnum, std::string> codeToString = createCodeToString();
return codeToString;
}

Expand All @@ -135,67 +183,6 @@ AnatomicalOrientation::GetStringToCode()
}


std::map<typename AnatomicalOrientation::ToEnum, std::string>
AnatomicalOrientation::CreateCodeToString()
{
std::map<ToEnum, std::string> orientToString;
auto helperAddCode = [&orientToString](std::string str, ToEnum code) { orientToString[code] = std::move(str); };

// Map between axis string labels and SpatialOrientation
helperAddCode("RIP", ToEnum::RIP);
helperAddCode("LIP", ToEnum::LIP);
helperAddCode("RSP", ToEnum::RSP);
helperAddCode("LSP", ToEnum::LSP);
helperAddCode("RIA", ToEnum::RIA);
helperAddCode("LIA", ToEnum::LIA);
helperAddCode("RSA", ToEnum::RSA);
helperAddCode("LSA", ToEnum::LSA);
helperAddCode("IRP", ToEnum::IRP);
helperAddCode("ILP", ToEnum::ILP);
helperAddCode("SRP", ToEnum::SRP);
helperAddCode("SLP", ToEnum::SLP);
helperAddCode("IRA", ToEnum::IRA);
helperAddCode("ILA", ToEnum::ILA);
helperAddCode("SRA", ToEnum::SRA);
helperAddCode("SLA", ToEnum::SLA);
helperAddCode("RPI", ToEnum::RPI);
helperAddCode("LPI", ToEnum::LPI);
helperAddCode("RAI", ToEnum::RAI);
helperAddCode("LAI", ToEnum::LAI);
helperAddCode("RPS", ToEnum::RPS);
helperAddCode("LPS", ToEnum::LPS);
helperAddCode("RAS", ToEnum::RAS);
helperAddCode("LAS", ToEnum::LAS);
helperAddCode("PRI", ToEnum::PRI);
helperAddCode("PLI", ToEnum::PLI);
helperAddCode("ARI", ToEnum::ARI);
helperAddCode("ALI", ToEnum::ALI);
helperAddCode("PRS", ToEnum::PRS);
helperAddCode("PLS", ToEnum::PLS);
helperAddCode("ARS", ToEnum::ARS);
helperAddCode("ALS", ToEnum::ALS);
helperAddCode("IPR", ToEnum::IPR);
helperAddCode("SPR", ToEnum::SPR);
helperAddCode("IAR", ToEnum::IAR);
helperAddCode("SAR", ToEnum::SAR);
helperAddCode("IPL", ToEnum::IPL);
helperAddCode("SPL", ToEnum::SPL);
helperAddCode("IAL", ToEnum::IAL);
helperAddCode("SAL", ToEnum::SAL);
helperAddCode("PIR", ToEnum::PIR);
helperAddCode("PSR", ToEnum::PSR);
helperAddCode("AIR", ToEnum::AIR);
helperAddCode("ASR", ToEnum::ASR);
helperAddCode("PIL", ToEnum::PIL);
helperAddCode("PSL", ToEnum::PSL);
helperAddCode("AIL", ToEnum::AIL);
helperAddCode("ASL", ToEnum::ASL);
helperAddCode("INVALID", ToEnum::INVALID);

return orientToString;
}


typename AnatomicalOrientation::ToEnum
AnatomicalOrientation::DirectionCosinesToOrientation(const DirectionType & dir)
{
Expand Down Expand Up @@ -322,15 +309,14 @@ operator<<(std::ostream & out, typename AnatomicalOrientation::CoordinateEnum va
std::ostream &
operator<<(std::ostream & out, typename AnatomicalOrientation::ToEnum value)
{

auto iter = AnatomicalOrientation::GetCodeToString().find(value);
if (iter == AnatomicalOrientation::GetCodeToString().end())
{
return (out << "invalid");
}
return (out << iter->second);
return (out << AnatomicalOrientation(value).GetAsToStringEncoding());
}

std::ostream &
operator<<(std::ostream & out, typename AnatomicalOrientation::FromEnum value)
{
return (out << AnatomicalOrientation(value).GetAsFromStringEncoding());
}

std::ostream &
operator<<(std::ostream & out, const AnatomicalOrientation & orientation)
Expand Down
22 changes: 11 additions & 11 deletions Modules/Core/Common/test/itkAnatomicalOrientationGTest.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ TEST(AnatomicalOrientation, ConstructionAndValues)

AnatomicalOrientation do1(OE::LPS);

EXPECT_EQ("LPS", do1.GetAsString());
EXPECT_EQ("LPS", do1.GetAsToStringEncoding());
EXPECT_EQ(CE::RightToLeft, do1.GetPrimaryTerm());
EXPECT_EQ(CE::AnteriorToPosterior, do1.GetSecondaryTerm());
EXPECT_EQ(CE::InferiorToSuperior, do1.GetTertiaryTerm());
Expand All @@ -45,9 +45,9 @@ TEST(AnatomicalOrientation, ConstructionAndValues)
EXPECT_EQ(d, do1.GetAsDirection());


do1 = AnatomicalOrientation("RAS");
do1 = AnatomicalOrientation::CreateFromToStringEncoding("RAS");

EXPECT_EQ("RAS", do1.GetAsString());
EXPECT_EQ("RAS", do1.GetAsToStringEncoding());
EXPECT_EQ(CE::LeftToRight, do1.GetPrimaryTerm());
EXPECT_EQ(CE::PosteriorToAnterior, do1.GetSecondaryTerm());
EXPECT_EQ(CE::InferiorToSuperior, do1.GetTertiaryTerm());
Expand All @@ -60,9 +60,9 @@ TEST(AnatomicalOrientation, ConstructionAndValues)
EXPECT_EQ(d, do1.GetAsDirection());


do1 = AnatomicalOrientation("rai");
do1 = AnatomicalOrientation::CreateFromToStringEncoding("rai");

EXPECT_EQ("RAI", do1.GetAsString());
EXPECT_EQ("RAI", do1.GetAsToStringEncoding());
EXPECT_EQ(CE::LeftToRight, do1.GetPrimaryTerm());
EXPECT_EQ(CE::PosteriorToAnterior, do1.GetSecondaryTerm());
EXPECT_EQ(CE::SuperiorToInferior, do1.GetTertiaryTerm());
Expand All @@ -71,7 +71,7 @@ TEST(AnatomicalOrientation, ConstructionAndValues)

do1 = AnatomicalOrientation(OE::PIR);

EXPECT_EQ("PIR", do1.GetAsString());
EXPECT_EQ("PIR", do1.GetAsToStringEncoding());
EXPECT_EQ(CE::AnteriorToPosterior, do1.GetPrimaryTerm());
EXPECT_EQ(CE::SuperiorToInferior, do1.GetSecondaryTerm());
EXPECT_EQ(CE::LeftToRight, do1.GetTertiaryTerm());
Expand All @@ -85,16 +85,16 @@ TEST(AnatomicalOrientation, ConstructionAndValues)

AnatomicalOrientation do2(d);

EXPECT_EQ("PIR", do2.GetAsString());
EXPECT_EQ("PIR", do2.GetAsToStringEncoding());
EXPECT_EQ(CE::AnteriorToPosterior, do2.GetPrimaryTerm());
EXPECT_EQ(CE::SuperiorToInferior, do2.GetSecondaryTerm());
EXPECT_EQ(CE::LeftToRight, do2.GetTertiaryTerm());
EXPECT_EQ(OE::PIR, do2.GetAsOrientation());

EXPECT_EQ(d, do2.GetAsDirection());

AnatomicalOrientation do3("something invalid");
EXPECT_EQ("INVALID", do3.GetAsString());
AnatomicalOrientation do3 = AnatomicalOrientation::CreateFromToStringEncoding("something invalid");
EXPECT_EQ("INVALID", do3.GetAsToStringEncoding());
EXPECT_EQ(CE::UNKNOWN, do3.GetPrimaryTerm());
EXPECT_EQ(CE::UNKNOWN, do3.GetSecondaryTerm());
EXPECT_EQ(CE::UNKNOWN, do3.GetTertiaryTerm());
Expand Down Expand Up @@ -222,7 +222,7 @@ TEST(AntomicalOrientation, ToFromEnumInteroperability)

EXPECT_EQ(itk_rai, itk::AnatomicalOrientation(OE::LPS));
EXPECT_EQ(itk_rai.GetAsOrientation(), OE::LPS);
EXPECT_EQ(itk_rai.GetAsString(), "LPS");
EXPECT_EQ(itk_rai.GetAsToStringEncoding(), "LPS");
const std::array<CE, 3> expected_terms = { { CE::RightToLeft, CE::AnteriorToPosterior, CE::InferiorToSuperior } };
EXPECT_EQ(itk_rai.GetTerms(), expected_terms);
}
Expand All @@ -244,6 +244,6 @@ TEST(AnatomicalOrientation, LegacyInteroperability)
itk::AnatomicalOrientation itk_rai(SOE::ITK_COORDINATE_ORIENTATION_RAI);
EXPECT_EQ(itk_rai, OE::LPS);
EXPECT_EQ(itk_rai.GetAsOrientation(), OE::LPS);
EXPECT_EQ(itk_rai.GetAsString(), "LPS");
EXPECT_EQ(itk_rai.GetAsToStringEncoding(), "LPS");
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ itkReadWriteImageWithDictionaryTest(int argc, char * argv[])
inputImage->Allocate();
inputImage->FillBuffer(0);

inputImage->SetDirection(itk::AnatomicalOrientation("LSA").GetAsDirection());
inputImage->SetDirection(itk::AnatomicalOrientation::CreateFromToStringEncoding("LSA").GetAsDirection());

// Add some metadata in the dictionary
itk::MetaDataDictionary & inputDictionary = inputImage->GetMetaDataDictionary();
Expand Down
Loading

0 comments on commit 7192104

Please sign in to comment.