Skip to content

Commit

Permalink
Merge pull request #4804 from blowekamp/add_anatomical_orientation
Browse files Browse the repository at this point in the history
Add AnatomicalOrientation class
  • Loading branch information
blowekamp authored Oct 23, 2024
2 parents 6815b4e + 1fb80ea commit 283ad56
Show file tree
Hide file tree
Showing 19 changed files with 1,365 additions and 422 deletions.
584 changes: 584 additions & 0 deletions Modules/Core/Common/include/itkAnatomicalOrientation.h

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Modules/Core/Common/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ set(ITKCommon_SRCS
itkFrustumSpatialFunction.cxx
itkObjectStore.cxx
itkGaussianDerivativeOperator.cxx
itkSpatialOrientation.cxx)
itkSpatialOrientation.cxx
itkAnatomicalOrientation.cxx
)

if(WIN32)
list(APPEND ITKCommon_SRCS itkWin32OutputWindow.cxx)
Expand Down
328 changes: 328 additions & 0 deletions Modules/Core/Common/src/itkAnatomicalOrientation.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#include "itkAnatomicalOrientation.h"
#ifndef ITK_FUTURE_LEGACY_REMOVE
# include "itkSpatialOrientationAdapter.h"
#endif

namespace itk
{


#ifndef ITK_FUTURE_LEGACY_REMOVE
AnatomicalOrientation::AnatomicalOrientation(LegacyOrientationType legacyOrientation)
: AnatomicalOrientation(SpatialOrientationAdapter().ToDirectionCosines(legacyOrientation))
{
assert(uint32_t(legacyOrientation) == uint32_t(m_Value));
}
#endif


std::string
AnatomicalOrientation::GetAsPositiveStringEncoding() const
{

// a lambda function to convert a CoordinateEnum to a char
auto enumAsChar = [](CoordinateEnum coord) -> char {
switch (coord)
{
case CoordinateEnum::RightToLeft:
return 'L';
case CoordinateEnum::LeftToRight:
return 'R';
case CoordinateEnum::AnteriorToPosterior:
return 'P';
case CoordinateEnum::PosteriorToAnterior:
return 'A';
case CoordinateEnum::InferiorToSuperior:
return 'S';
case CoordinateEnum::SuperiorToInferior:
return 'I';
default:
return 'X';
}
};

if (m_Value == PositiveEnum::INVALID)
{
return "INVALID";
}

return std::string({ enumAsChar(GetPrimaryTerm()), enumAsChar(GetSecondaryTerm()), enumAsChar(GetTertiaryTerm()) });
}


std::string
AnatomicalOrientation::GetAsNegativeStringEncoding() const
{
return ConvertStringEncoding(GetAsPositiveStringEncoding());
}


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

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

AnatomicalOrientation
AnatomicalOrientation::CreateFromNegativeStringEncoding(std::string str)
{
return AnatomicalOrientation::CreateFromPositiveStringEncoding(ConvertStringEncoding(str));
}


std::string
AnatomicalOrientation::ConvertStringEncoding(std::string str)
{

auto flip = [](char c) -> char {
switch (::toupper(c))
{
case 'R':
return 'L';
case 'L':
return 'R';
case 'A':
return 'P';
case 'P':
return 'A';
case 'S':
return 'I';
case 'I':
return 'S';
case 'X':
return 'X';
default:
return c;
}
};

for (auto & c : str)
{
c = flip(c);
}
return str;
}


AnatomicalOrientation::CoordinateEnum
AnatomicalOrientation::GetCoordinateTerm(CoordinateMajornessTermsEnum cmt) const
{
return static_cast<CoordinateEnum>(static_cast<uint32_t>(m_Value) >> static_cast<uint8_t>(cmt) & 0xff);
}


const std::map<typename AnatomicalOrientation::PositiveEnum, std::string> &
AnatomicalOrientation::GetCodeToString()
{
auto createCodeToString = []() -> std::map<PositiveEnum, std::string> {
std::map<PositiveEnum, std::string> orientToString;

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

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

const std::map<std::string, AnatomicalOrientation::PositiveEnum> &
AnatomicalOrientation::GetStringToCode()
{

auto createStringToCode = []() -> std::map<std::string, PositiveEnum> {
std::map<std::string, PositiveEnum> stringToCode;
const std::map<PositiveEnum, std::string> & codeToString = GetCodeToString();

for (const auto & kv : codeToString)
{
stringToCode[kv.second] = kv.first;
}
return stringToCode;
};

static const std::map<std::string, AnatomicalOrientation::PositiveEnum> stringToCode = createStringToCode();
return stringToCode;
}


AnatomicalOrientation::PositiveEnum
AnatomicalOrientation::ConvertDirectionToPositiveEnum(const DirectionType & dir)
{
// NOTE: This method was based off of itk::SpatialObjectAdaptor::FromDirectionCosines
// but it is DIFFERENT in the meaning of direction in terms of sign-ness.
CoordinateEnum terms[3] = { CoordinateEnum::UNKNOWN, CoordinateEnum::UNKNOWN, CoordinateEnum::UNKNOWN };

std::multimap<double, std::pair<unsigned, unsigned>> value_to_idx;
for (unsigned int c = 0; c < 3; ++c)
{
for (unsigned int r = 0; r < 3; ++r)
{
value_to_idx.emplace(std::abs(dir[c][r]), std::make_pair(c, r));
}
}

for (unsigned i = 0; i < 3; ++i)
{

auto max_idx = value_to_idx.rbegin()->second;
const unsigned int max_c = max_idx.first;
const unsigned int max_r = max_idx.second;

const int max_sgn = Math::sgn(dir[max_c][max_r]);

for (auto it = value_to_idx.begin(); it != value_to_idx.end();)
{
if (it->second.first == max_c || it->second.second == max_r)
{
value_to_idx.erase(it++);
}
else
{
++it;
}
}

switch (max_c)
{
case 0:
{
// When the dominant axis sign is positive, assign the coordinate for the direction we are increasing towards.
// ITK is in LPS, so that is the positive direction
terms[max_r] = (max_sgn == 1) ? CoordinateEnum::RightToLeft : CoordinateEnum::LeftToRight;
break;
}
case 1:
{
terms[max_r] = (max_sgn == 1) ? CoordinateEnum::AnteriorToPosterior : CoordinateEnum::PosteriorToAnterior;
break;
}
case 2:
{
terms[max_r] = (max_sgn == 1) ? CoordinateEnum::InferiorToSuperior : CoordinateEnum::SuperiorToInferior;
break;
}
default:
itkGenericExceptionMacro("Unexpected Axis");
}
}

return AnatomicalOrientation(terms[0], terms[1], terms[2]);
}


typename AnatomicalOrientation::DirectionType
AnatomicalOrientation::ConvertPositiveEnumToDirection(PositiveEnum orientationEnum)
{
const AnatomicalOrientation o(orientationEnum);

CoordinateEnum terms[Dimension] = { o.GetPrimaryTerm(), o.GetSecondaryTerm(), o.GetTertiaryTerm() };
DirectionType direction;
direction.Fill(0.0);

for (unsigned int i = 0; i < Dimension; ++i)
{
const int sign = (static_cast<uint8_t>(terms[i]) & 0x1) ? 1 : -1;

switch (terms[i])
{
case CoordinateEnum::LeftToRight:
case CoordinateEnum::RightToLeft:
direction[0][i] = -1 * sign;
break;
case CoordinateEnum::AnteriorToPosterior:
case CoordinateEnum::PosteriorToAnterior:
direction[1][i] = 1 * sign;
break;
case CoordinateEnum::InferiorToSuperior:
case CoordinateEnum::SuperiorToInferior:
direction[2][i] = -1 * sign;
break;
case CoordinateEnum::UNKNOWN:
break;
}
}
return direction;
}

std::ostream &
operator<<(std::ostream & out, typename AnatomicalOrientation::CoordinateEnum value)
{
switch (value)
{
case AnatomicalOrientation::CoordinateEnum::RightToLeft:
return out << "right-to-left";
case AnatomicalOrientation::CoordinateEnum::LeftToRight:
return out << "left-to-right";
case AnatomicalOrientation::CoordinateEnum::AnteriorToPosterior:
return out << "anterior-to-posterior";
case AnatomicalOrientation::CoordinateEnum::PosteriorToAnterior:
return out << "posterior-to-anterior";
case AnatomicalOrientation::CoordinateEnum::InferiorToSuperior:
return out << "inferior-to-superior";
case AnatomicalOrientation::CoordinateEnum::SuperiorToInferior:
return out << "superior-to-inferior";
case AnatomicalOrientation::CoordinateEnum::UNKNOWN:
return out << "unknown";
default:
return out << "invalid";
}
}

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

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

std::ostream &
operator<<(std::ostream & out, const AnatomicalOrientation & orientation)
{
const auto terms = orientation.GetTerms();
static_assert(std::tuple_size<decltype(terms)>{} == 3);
return out << terms[0] << " " << terms[1] << " " << terms[2];
}

} // namespace itk
4 changes: 3 additions & 1 deletion Modules/Core/Common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,9 @@ set(ITKCommonGTests
itkWeakPointerGTest.cxx
itkCommonTypeTraitsGTest.cxx
itkMetaDataDictionaryGTest.cxx
itkSpatialOrientationAdaptorGTest.cxx)
itkSpatialOrientationAdaptorGTest.cxx
itkAnatomicalOrientationGTest.cxx
)
creategoogletestdriver(ITKCommon "${ITKCommon-Test_LIBRARIES}" "${ITKCommonGTests}")
# If `-static` was passed to CMAKE_EXE_LINKER_FLAGS, compilation fails. No need to
# test this case.
Expand Down
Loading

0 comments on commit 283ad56

Please sign in to comment.