diff --git a/code/indices/rttbGammaIndex.cpp b/code/indices/rttbGammaIndex.cpp index 632fad0..f20b4f5 100644 --- a/code/indices/rttbGammaIndex.cpp +++ b/code/indices/rttbGammaIndex.cpp @@ -1,75 +1,277 @@ // ----------------------------------------------------------------------- // RTToolbox - DKFZ radiotherapy quantitative evaluation library // // Copyright (c) German Cancer Research Center (DKFZ), // Software development for Integrated Diagnostics and Therapy (SIDT). // ALL RIGHTS RESERVED. // See rttbCopyright.txt or // http://www.dkfz.de/en/sidt/projects/rttb/copyright.html // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notices for more information. // //------------------------------------------------------------------------ #include "rttbGammaIndex.h" -#include "rttbException.h" -#include "rttbInvalidParameterException.h" +#include "rttbExceptionMacros.h" +#include "rttbNullPointerException.h" +#include "rttbIndexOutOfBoundsException.h" +#include "rttbInvalidDoseException.h" +#include "rttbLinearInterpolation.h" + +#include namespace rttb { - namespace indices - { - - //SpatialDoseIndex::SpatialDoseIndex(DoseTypeGy aDoseReference) - // : _doseReference(aDoseReference), _initSuccess(false) {} - - // SpatialDoseIndex::~SpatialDoseIndex()= default; - - //bool SpatialDoseIndex::init() - //{ - // if (!(this->checkInputs())) - // { - // throw core::InvalidParameterException("Check inputs failed: invalid parameters! "); - // } - - // if (this->calcIndex()) - // { - // _initSuccess = true; - // } - // else - // { - // throw core::InvalidParameterException("Index calculation failed! "); - // } - - // return _initSuccess; - //} - - //void SpatialDoseIndex::setDoseReference(DoseTypeGy aDoseReference) - //{ - // _doseReference = aDoseReference; - // _initSuccess = false; - // init(); - //} - - //DoseTypeGy SpatialDoseIndex::getDoseReference() const - //{ - // return _doseReference; - //} - - //IndexValueType SpatialDoseIndex::getValue() const - //{ - // if (_initSuccess) - // { - // return _value; - // } - // else - // { - // throw core::Exception("SpatialDoseIndex init error: init() must be called first!"); - // } - //} - } + namespace indices + { + GammaIndex::DTAPreComputation::DTAPreComputation(WorldCoordinate3D pos, DTAValueType disPen, DTAValueType zeroDoseDiffPen): searchPosition(pos), distancePenalty(disPen), penaltyWithZeroDoseDiff(zeroDoseDiffPen) + { + } + + GammaIndex::GammaIndex(core::DoseAccessorInterface::ConstPointer dose, + core::DoseAccessorInterface::ConstPointer referenceDose) : SpatialDoseIndex(dose), _referenceDose(referenceDose) + { + if (nullptr == referenceDose) + { + rttbExceptionMacro(core::NullPointerException, << "referenceDose must not be nullptr!"); + } + + _indexGeometry = dose->getGeometricInfo().clone(); + setDoseInterpolator(nullptr); + setReferenceDoseInterpolator(nullptr); + this->UpdatePrecomputedDistancePenalties(); + } + + GammaIndex::GammaIndex(core::DoseAccessorInterface::ConstPointer dose, + core::DoseAccessorInterface::ConstPointer referenceDose, + core::GeometricInfo indexGeometry) : SpatialDoseIndex(dose), _referenceDose(referenceDose) + { + if (nullptr == referenceDose) + { + rttbExceptionMacro(core::NullPointerException, << "referenceDose must not be nullptr!"); + } + + _indexGeometry = indexGeometry.clone(); + setDoseInterpolator(nullptr); + setReferenceDoseInterpolator(nullptr); + this->UpdatePrecomputedDistancePenalties(); + } + + const core::GeometricInfo& GammaIndex::getGeometricInfo() const + { + return *_indexGeometry; + } + + GenericValueType GammaIndex::getValueAt(const VoxelGridID aID) const + { + VoxelGridIndex3D gridIndex; + if (!_indexGeometry->convert(aID, gridIndex)) + { + rttbExceptionMacro(core::IndexOutOfBoundsException, << "Cannot get gamma index by grid ID. ID is not valid for geometric info of the gamma index. Invalid ID: " << aID); + } + return this->getValueAt(gridIndex); + } + + GenericValueType GammaIndex::getValueAt(const VoxelGridIndex3D& aIndex) const + { + WorldCoordinate3D aPoint; + if (!_indexGeometry->indexToWorldCoordinate(aIndex, aPoint)) + { + rttbExceptionMacro(core::IndexOutOfBoundsException, << "Cannot get gamma index by grid index. Grid index is not valid for geometric info of the gamma index. Invalid grid index: " << aIndex); + } + return computeValueAndPosition(aPoint).first; + } + + GenericValueType GammaIndex::getValueAt(const WorldCoordinate3D& aPoint) const + { + if (!_indexGeometry->isInside(aPoint)) + { + rttbExceptionMacro(core::IndexOutOfBoundsException, << "Cannot get gamma index by point. Point is not valid for geometric info of the gamma index. Invalid point: " << aPoint); + } + + return computeValueAndPosition(aPoint).first; + } + + const IDType GammaIndex::getUID() const + { + std::stringstream uidStream; + uidStream << "gammaindex." << _dta << "." << _searchSamplingRate << "." << _ddt << "." << _useLocalDose << "." << _globalDose << "_" << _dose->getUID() << "_" << _referenceDose->getUID(); + return uidStream.str(); + } + + void GammaIndex::setDoseInterpolator(interpolation::InterpolationBase::Pointer interpolator) + { + if (nullptr == interpolator) + { + _doseInterpolator = ::boost::make_shared(); + } + else + { + _doseInterpolator = interpolator; + } + _doseInterpolator->setAccessorPointer(_dose); + } + + interpolation::InterpolationBase::ConstPointer GammaIndex::getDoseInterpolator() const + { + return _doseInterpolator; + }; + + void GammaIndex::setReferenceDoseInterpolator(interpolation::InterpolationBase::Pointer interpolator) + { + if (nullptr == interpolator) + { + _referenceDoseInterpolator = ::boost::make_shared(); + } + else + { + _referenceDoseInterpolator = interpolator; + } + _referenceDoseInterpolator->setAccessorPointer(_referenceDose); + } + + interpolation::InterpolationBase::ConstPointer GammaIndex::getReferenceDoseInterpolator() const + { + return _referenceDoseInterpolator; + } + + void GammaIndex::setDistanceToAgreementThreshold(DTAValueType dta) + { + if (dta != _dta) + { + _dta = dta; + this->UpdatePrecomputedDistancePenalties(); + } + } + + GammaIndex::DTAValueType GammaIndex::getDistanceToAgreementThreshold() const + { + return _dta; + } + + void GammaIndex::setDoseDifferenceThreshold(DDTValueType ddt) + { + _ddt = ddt; + } + + GammaIndex::DDTValueType GammaIndex::getDoseDifferenceThreshold() const + { + return _ddt; + } + + void GammaIndex::setSearchSamplingRate(unsigned int rate) + { + if (rate != _searchSamplingRate) + { + _searchSamplingRate = rate; + this->UpdatePrecomputedDistancePenalties(); + } + } + + unsigned int GammaIndex::getSearchSamplingRate() const + { + return _searchSamplingRate; + } + + void GammaIndex::setUseLocalDose(bool useLocalDose) + { + _useLocalDose = useLocalDose; + } + + bool GammaIndex::getUseLocalDose() const + { + return _useLocalDose; + } + + void GammaIndex::setGlobalDose(DoseTypeGy globalDose) + { + _globalDose = globalDose; + } + + DoseTypeGy GammaIndex::getGlobalDose() const + { + return _globalDose; + } + + void GammaIndex::UpdatePrecomputedDistancePenalties() + { + DATPreComputationVectorType newPenalties; + const auto min = -1 * static_cast(_searchSamplingRate); + const auto max = static_cast(_searchSamplingRate); + + /*We add the penalty for the search center (measured point) + explicitly at the beginning to check it first. This allows + us to shortcut in cases where the measured point equals the + expected or is very close.*/ + newPenalties.emplace_back(WorldCoordinate3D( 0.,0.,0. ), 0., 0.); + + for (int iX = min; iX <= max; ++iX) + { + const WorldCoordinate x = _dta * iX / static_cast(_searchSamplingRate); + for (int iY = min; iY <= max; ++iY) + { + const WorldCoordinate y = _dta * iY / static_cast(_searchSamplingRate); + for (int iZ = min; iZ <= max; ++iZ) + { + const WorldCoordinate z = _dta * iZ / static_cast(_searchSamplingRate); + WorldCoordinate3D newPos = { x,y,z }; + const auto newDistance = boost::numeric::ublas::norm_2(newPos); + const auto penalty = (newDistance * newDistance) / (_dta * _dta); + if (penalty>0 && penalty <= 1) + { //we skip the origin (penalty == 0) as it was added before the loop + //and we skip every point that would not pass because the dta penalty is to high (>1) + newPenalties.emplace_back(newPos, penalty, std::sqrt(penalty)); + } + } + } + } + _precomputedDistancePenalties = newPenalties; + } + + std::pair GammaIndex::computeValueAndPosition(const WorldCoordinate3D& aPoint) const + { + const auto measuredDose = _doseInterpolator->getValue(aPoint); + + const DoseTypeGy doseThresholdGy = ((_useLocalDose) ? measuredDose : _globalDose) * _ddt; + const DoseTypeGy doseThresholdGySquared = doseThresholdGy*doseThresholdGy; + + if (0. == doseThresholdGySquared) + { + //TODO needs to clarify what to do if dose threshold is 0 because we would devide through it?!?? + return std::make_pair(std::nan(""),WorldCoordinate3D(0.)); + } + + std::pair bestFinding; + bestFinding.second = WorldCoordinate3D(std::numeric_limits::max()); + bestFinding.first = std::numeric_limits::max(); + + for (const auto& distancePenalty : _precomputedDistancePenalties) + { + if (distancePenalty.penaltyWithZeroDoseDiff < bestFinding.first) + { //search position could beat the best finding -> evaluate it + const WorldCoordinate3D refPoint = aPoint + distancePenalty.searchPosition; + + if (_indexGeometry->isInside(refPoint) && _referenceDose->getGeometricInfo().isInside(refPoint)) + { //needed refpoint is part of index and reference dose geometry -> go on + const auto refDose = _referenceDoseInterpolator->getValue(refPoint); + const auto doseDifferenceSquared = std::pow(refDose - measuredDose, 2); + const auto dosePenalty = doseDifferenceSquared / doseThresholdGySquared; + const GenericValueType penalty = std::sqrt(distancePenalty.distancePenalty + dosePenalty); + if (penalty < bestFinding.first) + { + bestFinding.first = penalty; + bestFinding.second = distancePenalty.searchPosition; + } + } + } + } + + return bestFinding; + } + + } } diff --git a/code/indices/rttbGammaIndex.h b/code/indices/rttbGammaIndex.h index 7583777..6dbd1ac 100644 --- a/code/indices/rttbGammaIndex.h +++ b/code/indices/rttbGammaIndex.h @@ -1,86 +1,178 @@ // ----------------------------------------------------------------------- // RTToolbox - DKFZ radiotherapy quantitative evaluation library // // Copyright (c) German Cancer Research Center (DKFZ), // Software development for Integrated Diagnostics and Therapy (SIDT). // ALL RIGHTS RESERVED. // See rttbCopyright.txt or // http://www.dkfz.de/en/sidt/projects/rttb/copyright.html // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notices for more information. // //------------------------------------------------------------------------ #ifndef __GAMMA_INDEX_H #define __GAMMA_INDEX_H -#include "rttbSpatialDoseIndex.h" +#include +#include "rttbSpatialDoseIndex.h" #include "rttbInterpolationBase.h" #include "RTTBIndicesExports.h" namespace rttb { namespace indices { - /*! @class SpatialDoseIndex - @brief This is the base class for indicies that are computed on spatialy resolved - dose distributions (e.g. gamma index). - In contrast to other "normal" DVH based indices that are not spatialy resolved (like - e.g. ConformityIndex), these type of indices are computed on a spatialy resolved dose - distribution for specifeid points in space. Thus this index type itself has a geometry - info and is spatialy distributed. + /*! @class GammaIndex + @brief TODO @ingroup indices */ class RTTBIndices_EXPORT GammaIndex : SpatialDoseIndex { public: rttbClassMacro(GammaIndex, SpatialDoseIndex); - /*! @brief Constructor with the referece dose*/ - GammaIndex(core::DoseAccessorInterface::ConstPointer computedDose, - core::DoseAccessorInterface::ConstPointer referenceDose); + /**Type used to specify the distance to agreement in mm.*/ + using DTAValueType = double; + /**Type used to specify the dose difference threshold in fractions (0= 0% and 0.5=50%).*/ + using DDTValueType = double; + + + /** Constructor that uses takes the dose and reference dose to compute the + gamma index. As geometric info (spatial resolution) of the index the geometric + info of dose will be used. + @pre dose must point to a valid instance. + @pre referenceDose must point to a valid instance.*/ + GammaIndex(core::DoseAccessorInterface::ConstPointer dose, + core::DoseAccessorInterface::ConstPointer referenceDose); + /** Constructor that allows to explicitly allows to specify the geometric info + of the index (e.g. if you want to have the index in another resolution. + @pre dose must point to a valid instance. + @pre referenceDose must point to a valid instance.*/ GammaIndex(core::DoseAccessorInterface::ConstPointer dose, - core::DoseAccessorInterface::ConstPointer referenceDose, - double distaneToAgreementThreshold, double doseDifferenceThreshold); - virtual ~GammaIndex(); + core::DoseAccessorInterface::ConstPointer referenceDose, + core::GeometricInfo indexGeometry); + + virtual ~GammaIndex() = default; GenericValueType getValueAt(const VoxelGridID aID) const override; GenericValueType getValueAt(const VoxelGridIndex3D& aIndex) const override; + GenericValueType getValueAt(const WorldCoordinate3D& aPoint) const; + const IDType getUID() const override; + const core::GeometricInfo& getGeometricInfo() const override; + + /**Allows to specify the interpolator that should be used if dose should be sampled. + If no interpolator is defined (nullptr), a LinarInterpolator will be used + (this is also the default after index construction). + Remark: After setting the interpolator, the index assumes that it takes control over + the interpolator and set the input accessor. Please do not change the state of the + interpolator in the lifetime of the index instance that took over control. Otherwise + strange side effects could happen in the computation.*/ + void setDoseInterpolator(interpolation::InterpolationBase::Pointer interpolator); + interpolation::InterpolationBase::ConstPointer getDoseInterpolator() const; + + /**Allows to specify the interpolator that should be used if referenceDose should be sampled. + If no interpolator is defined (nullptr), a LinarInterpolator will be used + (this is also the default after index construction). + Remark: After setting the interpolator, the index assumes that it takes control over + the interpolator and set the input accessor. Please do not change the state of the + interpolator in the lifetime of the index instance that took over control. Otherwise + strange side effects could happen in the computation.*/ + void setReferenceDoseInterpolator(interpolation::InterpolationBase::Pointer interpolator); + interpolation::InterpolationBase::ConstPointer getReferenceDoseInterpolator() const; + + void setDistanceToAgreementThreshold(DTAValueType dta); + DTAValueType getDistanceToAgreementThreshold() const; + + void setDoseDifferenceThreshold(DDTValueType ddt); + DDTValueType getDoseDifferenceThreshold() const; + + void setSearchSamplingRate(unsigned int rate); + unsigned int getSearchSamplingRate() const; + + void setUseLocalDose(bool useLocalDose); + bool getUseLocalDose() const; + + void setGlobalDose(DoseTypeGy globalDose); + DoseTypeGy getGlobalDose() const; + protected: + /** GeometricInfo that should be used for the index. Either the geometric info + of the dose or a user defined geometric info.*/ + core::GeometricInfo::ConstPointer _indexGeometry; + /** Reference dose used to compare the computed dose with.*/ core::DoseAccessorInterface::ConstPointer _referenceDose; + /** Interpolator used when the index needs to access values of _dose*/ interpolation::InterpolationBase::Pointer _doseInterpolator; + /** Interpolator used when the index needs to access values of _referenceDose*/ interpolation::InterpolationBase::Pointer _referenceDoseInterpolator; - /** Distance to aggreement (DTA) threshold for the gamma index comutation. Specified in mm.*/ - double _dtaThreshold = 3.; + /** Distance to aggreement (DTA) threshold for the gamma index comutation. Specified in mm. + It also specified the search range radius (in mm) arround the the center point (world coordinates) + requested for the index value.*/ + DTAValueType _dta = 3.; + + /** dose difference as fraction (0= 0% and 0.5=50%) that is the threshold*/ + DDTValueType _ddt = 0.03; /** To calculate the gamma index one has to sample the reference dose within the DTA. This variable controls how dense the sampling is within the Dit is sampled a Distance to aggreement (DTA) threshold for the gamma index comutation. Specified in mm.*/ - unsigned int _spatialResolution = 10; - - /** in %*/ - double _doseDifferenceThreshold = 3.; + unsigned int _searchSamplingRate = 5; /** */ - bool _useLocalDose = false; + bool _useLocalDose = true; DoseTypeGy _globalDose = 0.; + /** Internal helper that stores a search position vector that has been + computed given the specified distance to aggreement and the search sampling rate.*/ + struct DTAPreComputation + { + /** The search position is relative to the "measured" point for which + the gamma index should be computed. Thus search position(0, 0, 0) is + the "measured" point itself.*/ + WorldCoordinate3D searchPosition; + + /** The precomputed distance penalty part of the penalty formular.*/ + DTAValueType distancePenalty = 0.0; + + /** The complete penalty for the seach position, assuming that the dose + difference penalty is 0. This the sqrt(distancePenalty).*/ + DTAValueType penaltyWithZeroDoseDiff = 0.0; + + DTAPreComputation(WorldCoordinate3D pos, DTAValueType disPen, DTAValueType zeroDoseDiffPen); + DTAPreComputation() = default; + }; + + using DATPreComputationVectorType = std::vector< DTAPreComputation >; + /** Collection of all precomputed search positions and there penalties.*/ + DATPreComputationVectorType _precomputedDistancePenalties; + + /** function can be called to update the _precomputedDistancePenalties given the current + index settings. After the update only search position are in _precomputedDistancePenalties, + that do not fail the DTA.\n + The implementation makes use of the fact that the distance penalty + part of the gamma index does not depend on the dose distribution itself, and can be + precomputed for any dose distribution and any search position as soon as DTA and sampling rate + is defined.*/ + void UpdatePrecomputedDistancePenalties(); + + std::pair computeValueAndPosition(const WorldCoordinate3D& aPoint) const; }; } } #endif diff --git a/code/indices/rttbSpatialDoseIndex.cpp b/code/indices/rttbSpatialDoseIndex.cpp index 2bf8c09..8d632ba 100644 --- a/code/indices/rttbSpatialDoseIndex.cpp +++ b/code/indices/rttbSpatialDoseIndex.cpp @@ -1,75 +1,41 @@ // ----------------------------------------------------------------------- // RTToolbox - DKFZ radiotherapy quantitative evaluation library // // Copyright (c) German Cancer Research Center (DKFZ), // Software development for Integrated Diagnostics and Therapy (SIDT). // ALL RIGHTS RESERVED. // See rttbCopyright.txt or // http://www.dkfz.de/en/sidt/projects/rttb/copyright.html // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notices for more information. // //------------------------------------------------------------------------ #include "rttbSpatialDoseIndex.h" -#include "rttbException.h" -#include "rttbInvalidParameterException.h" +#include "rttbExceptionMacros.h" +#include "rttbNullPointerException.h" namespace rttb { - namespace indices - { - - //SpatialDoseIndex::SpatialDoseIndex(DoseTypeGy aDoseReference) - // : _doseReference(aDoseReference), _initSuccess(false) {} - - // SpatialDoseIndex::~SpatialDoseIndex()= default; - - //bool SpatialDoseIndex::init() - //{ - // if (!(this->checkInputs())) - // { - // throw core::InvalidParameterException("Check inputs failed: invalid parameters! "); - // } - - // if (this->calcIndex()) - // { - // _initSuccess = true; - // } - // else - // { - // throw core::InvalidParameterException("Index calculation failed! "); - // } - - // return _initSuccess; - //} - - //void SpatialDoseIndex::setDoseReference(DoseTypeGy aDoseReference) - //{ - // _doseReference = aDoseReference; - // _initSuccess = false; - // init(); - //} - - //DoseTypeGy SpatialDoseIndex::getDoseReference() const - //{ - // return _doseReference; - //} - - //IndexValueType SpatialDoseIndex::getValue() const - //{ - // if (_initSuccess) - // { - // return _value; - // } - // else - // { - // throw core::Exception("SpatialDoseIndex init error: init() must be called first!"); - // } - //} - } + namespace indices + { + + SpatialDoseIndex::SpatialDoseIndex(core::DoseAccessorInterface::ConstPointer aDose) + : _dose(aDose) + { + if (nullptr == aDose) + { + rttbExceptionMacro(core::NullPointerException, "aDose must not be nullptr!"); + } + } + + const core::GeometricInfo& SpatialDoseIndex::getGeometricInfo() const + { + return _dose->getGeometricInfo(); + } + } } diff --git a/code/indices/rttbSpatialDoseIndex.h b/code/indices/rttbSpatialDoseIndex.h index 58c814b..10e9194 100644 --- a/code/indices/rttbSpatialDoseIndex.h +++ b/code/indices/rttbSpatialDoseIndex.h @@ -1,55 +1,56 @@ // ----------------------------------------------------------------------- // RTToolbox - DKFZ radiotherapy quantitative evaluation library // // Copyright (c) German Cancer Research Center (DKFZ), // Software development for Integrated Diagnostics and Therapy (SIDT). // ALL RIGHTS RESERVED. // See rttbCopyright.txt or // http://www.dkfz.de/en/sidt/projects/rttb/copyright.html // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notices for more information. // //------------------------------------------------------------------------ #ifndef __SPATIAL_DOSE_INDEX_H #define __SPATIAL_DOSE_INDEX_H #include "rttbDoseAccessorInterface.h" #include "RTTBIndicesExports.h" namespace rttb { namespace indices { /*! @class SpatialDoseIndex @brief This is the base class for indicies that are computed on spatialy resolved dose distributions (e.g. gamma index). In contrast to other "normal" DVH based indices that are not spatialy resolved (like e.g. ConformityIndex), these type of indices are computed on a spatialy resolved dose distribution for specifeid points in space. Thus this index type itself has a geometry info and is spatialy distributed. @ingroup indices */ class RTTBIndices_EXPORT SpatialDoseIndex : core::AccessorInterface { public: rttbClassMacro(SpatialDoseIndex, AccessorInterface); const core::GeometricInfo& getGeometricInfo() const override; protected: - /*! @brief Constructor with the referece dose*/ + /*! @brief Constructor with the referece dose. + @pre dose pointer must be valid.*/ SpatialDoseIndex(core::DoseAccessorInterface::ConstPointer aDose); - virtual ~SpatialDoseIndex(); + virtual ~SpatialDoseIndex() = default; core::DoseAccessorInterface::ConstPointer _dose; }; } } #endif diff --git a/testing/indices/CMakeLists.txt b/testing/indices/CMakeLists.txt index bfe301b..71921fc 100644 --- a/testing/indices/CMakeLists.txt +++ b/testing/indices/CMakeLists.txt @@ -1,32 +1,33 @@ #----------------------------------------------------------------------------- # Setup the system information test. Write out some basic failsafe # information in case the test doesn't run. #----------------------------------------------------------------------------- SET(INDICES_TESTS ${EXECUTABLE_OUTPUT_PATH}/${RTToolbox_PREFIX}IndicesTests) SET(INDICES_HEADER_TEST ${EXECUTABLE_OUTPUT_PATH}/${RTToolbox_PREFIX}CoreIndicesTest) SET(TEMP ${RTTBTesting_BINARY_DIR}/temporary) #----------------------------------------------------------------------------- ADD_TEST(ConformationNumberTest ${INDICES_TESTS} ConformationNumberTest "${TEST_DATA_ROOT}/DVH/XML/dvh_test_TV.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT1.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT2.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT3.xml") ADD_TEST(ConformalIndexTest ${INDICES_TESTS} ConformalIndexTest "${TEST_DATA_ROOT}/DVH/XML/dvh_test_TV.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT1.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT2.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT3.xml") ADD_TEST(ConformityIndexTest ${INDICES_TESTS} ConformityIndexTest "${TEST_DATA_ROOT}/DVH/XML/dvh_test_TV.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT1.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT2.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT3.xml") ADD_TEST(CoverageIndexTest ${INDICES_TESTS} CoverageIndexTest "${TEST_DATA_ROOT}/DVH/XML/dvh_test_TV.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT1.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT2.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT3.xml") ADD_TEST(HomogeneityIndexTest ${INDICES_TESTS} HomogeneityIndexTest "${TEST_DATA_ROOT}/DVH/XML/dvh_test_TV.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT1.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT2.xml" "${TEST_DATA_ROOT}/DVH/XML/dvh_test_HT3.xml") +ADD_TEST(GammaIndexTest ${INDICES_TESTS} GammaIndexTest) -RTTB_CREATE_TEST_MODULE(Indices DEPENDS RTTBCore RTTBIndices RTTBOtherIO PACKAGE_DEPENDS Boost Litmus) +RTTB_CREATE_TEST_MODULE(Indices DEPENDS RTTBCore RTTBIndices RTTBTestHelper RTTBOtherIO PACKAGE_DEPENDS Boost Litmus) diff --git a/testing/indices/GammaIndexTest.cpp b/testing/indices/GammaIndexTest.cpp new file mode 100644 index 0000000..58640f9 --- /dev/null +++ b/testing/indices/GammaIndexTest.cpp @@ -0,0 +1,165 @@ +// ----------------------------------------------------------------------- +// RTToolbox - DKFZ radiotherapy quantitative evaluation library +// +// Copyright (c) German Cancer Research Center (DKFZ), +// Software development for Integrated Diagnostics and Therapy (SIDT). +// ALL RIGHTS RESERVED. +// See rttbCopyright.txt or +// http://www.dkfz.de/en/sidt/projects/rttb/copyright.html [^] +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notices for more information. +// +//------------------------------------------------------------------------ + +// this file defines the rttbCoreTests for the test driver +// and all it expects is that you have a function called RegisterTests + +#include "litCheckMacros.h" +#include "rttbDoseIndex.h" +#include "rttbBaseType.h" +#include "rttbNullPointerException.h" +#include "rttbGammaIndex.h" +#include "rttbException.h" +#include "rttbInvalidDoseException.h" +#include "rttbNullPointerException.h" +#include "rttbLinearInterpolation.h" +#include "rttbNearestNeighborInterpolation.h" + +#include "DummyDoseAccessor.h" + +#include + +#include +#include + +namespace rttb +{ + namespace testing + { + void Test_Initialization() + { + PREPARE_DEFAULT_TEST_REPORTING; + + + core::DoseAccessorInterface::ConstPointer invalidDose; + core::DoseAccessorInterface::ConstPointer simpleDose = boost::make_shared(); + + CHECK_THROW_EXPLICIT(indices::GammaIndex(invalidDose, invalidDose), + core::NullPointerException); + CHECK_THROW_EXPLICIT(indices::GammaIndex(invalidDose, invalidDose,core::GeometricInfo()), + core::NullPointerException); + CHECK_THROW_EXPLICIT(indices::GammaIndex(simpleDose, invalidDose), + core::NullPointerException); + CHECK_THROW_EXPLICIT(indices::GammaIndex(simpleDose, invalidDose, core::GeometricInfo()), + core::NullPointerException); + CHECK_THROW_EXPLICIT(indices::GammaIndex(invalidDose, simpleDose), + core::NullPointerException); + CHECK_THROW_EXPLICIT(indices::GammaIndex(invalidDose, simpleDose, core::GeometricInfo()), + core::NullPointerException); + + core::GeometricInfo geoInfo; + geoInfo.setImageSize({ 2,2,2 }); + geoInfo.setSpacing({ 1,1,1 }); + + core::GeometricInfo geoInfo2; + geoInfo2.setImageSize({ 3,3,3 }); + geoInfo2.setSpacing({ 1,1,1 }); + + core::DoseAccessorInterface::ConstPointer refDose = boost::make_shared(std::vector({ 1,2,3,4,5,6,7,8 }), geoInfo); + + indices::GammaIndex gamma(simpleDose, refDose); + indices::GammaIndex gamma2(simpleDose, refDose, geoInfo2); + + CHECK_EQUAL(simpleDose->getGeometricInfo(), gamma.getGeometricInfo()); + CHECK_EQUAL(geoInfo2, gamma2.getGeometricInfo()); + + //CHECK interpolator setting + CHECK(nullptr != dynamic_cast(gamma.getDoseInterpolator().get())); + CHECK(gamma.getDoseInterpolator()->getAccessorPointer() == simpleDose); + CHECK(nullptr != dynamic_cast(gamma.getReferenceDoseInterpolator().get())); + CHECK(gamma.getReferenceDoseInterpolator()->getAccessorPointer() == refDose); + + auto nnInterpolator = boost::make_shared(); + auto nnInterpolator2 = boost::make_shared(); + + gamma.setDoseInterpolator(nnInterpolator); + CHECK(nnInterpolator == gamma.getDoseInterpolator()); + CHECK(nnInterpolator->getAccessorPointer() == simpleDose); + CHECK(nullptr != dynamic_cast(gamma.getReferenceDoseInterpolator().get())); + + gamma.setReferenceDoseInterpolator(nnInterpolator2); + CHECK(nnInterpolator == gamma.getDoseInterpolator()); + CHECK(nnInterpolator->getAccessorPointer() == simpleDose); + CHECK(nnInterpolator2 == gamma.getReferenceDoseInterpolator()); + CHECK(nnInterpolator2->getAccessorPointer() == refDose); + + gamma.setDoseInterpolator(nullptr); + CHECK(nullptr != dynamic_cast(gamma.getDoseInterpolator().get())); + CHECK(gamma.getDoseInterpolator()->getAccessorPointer() == simpleDose); + CHECK(nnInterpolator2 == gamma.getReferenceDoseInterpolator()); + CHECK(nnInterpolator2->getAccessorPointer() == refDose); + + gamma.setReferenceDoseInterpolator(nullptr); + CHECK(nullptr != dynamic_cast(gamma.getDoseInterpolator().get())); + CHECK(gamma.getDoseInterpolator()->getAccessorPointer() == simpleDose); + CHECK(nullptr != dynamic_cast(gamma.getReferenceDoseInterpolator().get())); + CHECK(gamma.getReferenceDoseInterpolator()->getAccessorPointer() == refDose); + + //CHECK other settings + CHECK_EQUAL(3., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.03, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(5u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(true, gamma.getUseLocalDose()); + CHECK_EQUAL(0., gamma.getGlobalDose()); + + gamma.setDistanceToAgreementThreshold(2.); + CHECK_EQUAL(2., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.03, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(5u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(true, gamma.getUseLocalDose()); + CHECK_EQUAL(0., gamma.getGlobalDose()); + + gamma.setDoseDifferenceThreshold(0.1); + CHECK_EQUAL(2., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.1, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(5u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(true, gamma.getUseLocalDose()); + CHECK_EQUAL(0., gamma.getGlobalDose()); + + gamma.setSearchSamplingRate(3); + CHECK_EQUAL(2., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.1, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(3u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(true, gamma.getUseLocalDose()); + CHECK_EQUAL(0., gamma.getGlobalDose()); + + gamma.setUseLocalDose(false); + CHECK_EQUAL(2., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.1, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(3u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(false, gamma.getUseLocalDose()); + CHECK_EQUAL(0., gamma.getGlobalDose()); + + gamma.setGlobalDose(42.); + CHECK_EQUAL(2., gamma.getDistanceToAgreementThreshold()); + CHECK_EQUAL(0.1, gamma.getDoseDifferenceThreshold()); + CHECK_EQUAL(3u, gamma.getSearchSamplingRate()); + CHECK_EQUAL(false, gamma.getUseLocalDose()); + CHECK_EQUAL(42., gamma.getGlobalDose()); + } + + /*! @brief Test of GammaIndex.*/ + int GammaIndexTest(int /*argc*/, char* /*argv*/[]) + { + PREPARE_DEFAULT_TEST_REPORTING; + + Test_Initialization(); + + + RETURN_AND_REPORT_TEST_SUCCESS; + } + + }//testing +}//rttb \ No newline at end of file diff --git a/testing/indices/files.cmake b/testing/indices/files.cmake index 27624aa..81004cd 100644 --- a/testing/indices/files.cmake +++ b/testing/indices/files.cmake @@ -1,11 +1,12 @@ SET(CPP_FILES rttbIndicesTests.cpp ConformationNumberTest.cpp ConformalIndexTest.cpp ConformityIndexTest.cpp CoverageIndexTest.cpp HomogeneityIndexTest.cpp + GammaIndexTest.cpp ) SET(H_FILES ) diff --git a/testing/indices/rttbIndicesTests.cpp b/testing/indices/rttbIndicesTests.cpp index e79fc79..44068d4 100644 --- a/testing/indices/rttbIndicesTests.cpp +++ b/testing/indices/rttbIndicesTests.cpp @@ -1,58 +1,59 @@ // ----------------------------------------------------------------------- // RTToolbox - DKFZ radiotherapy quantitative evaluation library // // Copyright (c) German Cancer Research Center (DKFZ), // Software development for Integrated Diagnostics and Therapy (SIDT). // ALL RIGHTS RESERVED. // See rttbCopyright.txt or // http://www.dkfz.de/en/sidt/projects/rttb/copyright.html // // This software is distributed WITHOUT ANY WARRANTY; without even // the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. See the above copyright notices for more information. // //------------------------------------------------------------------------ // this file defines the rttbCoreTests for the test driver // and all it expects is that you have a function called RegisterTests #if defined(_MSC_VER) #pragma warning ( disable : 4786 ) #endif #include "litMultiTestsMain.h" namespace rttb { namespace testing { void registerTests() { LIT_REGISTER_TEST(ConformationNumberTest); LIT_REGISTER_TEST(ConformalIndexTest); LIT_REGISTER_TEST(ConformityIndexTest); LIT_REGISTER_TEST(CoverageIndexTest); LIT_REGISTER_TEST(HomogeneityIndexTest); + LIT_REGISTER_TEST(GammaIndexTest); } } } int main(int argc, char* argv[]) { int result = 0; rttb::testing::registerTests(); try { result = lit::multiTestsMain(argc, argv); } catch (...) { result = -1; } return result; }