diff --git a/Anima/diffusion/mcm/CMakeLists.txt b/Anima/diffusion/mcm/CMakeLists.txt index 4168a54e9..e35bae16d 100644 --- a/Anima/diffusion/mcm/CMakeLists.txt +++ b/Anima/diffusion/mcm/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} AnimaMCMBase AnimaSpecialFunctions + AnimaStatisticalDistributions ITKOptimizers ITKCommon ${TinyXML2_LIBRARY} diff --git a/Anima/diffusion/mcm/animaNODDICompartment.cxx b/Anima/diffusion/mcm/animaNODDICompartment.cxx index b96299c38..793e95d7b 100644 --- a/Anima/diffusion/mcm/animaNODDICompartment.cxx +++ b/Anima/diffusion/mcm/animaNODDICompartment.cxx @@ -494,7 +494,8 @@ void NODDICompartment::UpdateKappaValues() double dawsonValue = anima::EvaluateDawsonIntegral(std::sqrt(kappa), true); m_Tau1 = (1.0 / dawsonValue - 1.0) / (2.0 * kappa); m_Tau1Deriv = (1.0 - (1.0 - dawsonValue * (2.0 * kappa - 1.0)) / (2.0 * dawsonValue * dawsonValue)) / (2.0 * kappa * kappa); - anima::GetStandardWatsonSHCoefficients(kappa,m_WatsonSHCoefficients,m_WatsonSHCoefficientDerivatives); + m_WatsonDistribution.SetConcentrationParameter(kappa); + m_WatsonDistribution.GetStandardWatsonSHCoefficients(m_WatsonSHCoefficients, m_WatsonSHCoefficientDerivatives); m_ModifiedConcentration = false; } diff --git a/Anima/diffusion/mcm/animaNODDICompartment.h b/Anima/diffusion/mcm/animaNODDICompartment.h index 85667743a..8178e4723 100644 --- a/Anima/diffusion/mcm/animaNODDICompartment.h +++ b/Anima/diffusion/mcm/animaNODDICompartment.h @@ -2,6 +2,7 @@ #include #include +#include namespace anima { @@ -84,6 +85,12 @@ class ANIMAMCM_EXPORT NODDICompartment : public BaseCompartment m_CurrentBValue = -1.0; m_CurrentGradient.fill(0.0); + + itk::Vector meanAxis; + meanAxis[0] = 0.0; + meanAxis[1] = 0.0; + meanAxis[2] = 1.0; + m_WatsonDistribution.SetMeanAxis(meanAxis); } virtual ~NODDICompartment() {} @@ -112,6 +119,8 @@ class ANIMAMCM_EXPORT NODDICompartment : public BaseCompartment double m_Tau1, m_Tau1Deriv; double m_ExtraAxonalSignal, m_IntraAxonalSignal; double m_IntraAngleDerivative, m_IntraKappaDerivative, m_IntraAxialDerivative; + + anima::WatsonDistribution m_WatsonDistribution; }; } //end namespace anima diff --git a/Anima/diffusion/mcm_tools/CMakeLists.txt b/Anima/diffusion/mcm_tools/CMakeLists.txt index 911579ba5..d4f164b32 100644 --- a/Anima/diffusion/mcm_tools/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/CMakeLists.txt @@ -2,7 +2,9 @@ add_subdirectory(dwi_simulation_from_mcm) add_subdirectory(generate_isoradius_ddi_surface) add_subdirectory(get_scalar_map_from_ddi) add_subdirectory(mcm_average_images) +add_subdirectory(mcm_merge_anisotropic_weights) add_subdirectory(mcm_merge_block_images) +add_subdirectory(mcm_orientation_priors) add_subdirectory(mcm_scalar_maps) add_subdirectory(mt_estimation_validation) -add_subdirectory(test_averaging) \ No newline at end of file +add_subdirectory(test_averaging) diff --git a/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/CMakeLists.txt b/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/CMakeLists.txt new file mode 100644 index 000000000..949697d73 --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/CMakeLists.txt @@ -0,0 +1,37 @@ +if(BUILD_TOOLS) + +project(animaMCMMergeAnisotropicWeights) + +## ############################################################################# +## List Sources +## ############################################################################# + +list_source_files(${PROJECT_NAME} + ${CMAKE_CURRENT_SOURCE_DIR} + ) + +## ############################################################################# +## add executable +## ############################################################################# + +add_executable(${PROJECT_NAME} + ${${PROJECT_NAME}_CFILES} + ) + + +## ############################################################################# +## Link +## ############################################################################# + +target_link_libraries(${PROJECT_NAME} + ${ITKIO_LIBRARIES} + AnimaMCM + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/animaMCMMergeAnisotropicWeights.cxx b/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/animaMCMMergeAnisotropicWeights.cxx new file mode 100644 index 000000000..d1cb08480 --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_merge_anisotropic_weights/animaMCMMergeAnisotropicWeights.cxx @@ -0,0 +1,113 @@ +#include +#include +#include +#include + +#include +#include + +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg( + "i", "inputfile", + "A string specifying the name of the file that stores the input MCM image.", + true, "", "input mcm image", cmd); + TCLAP::ValueArg outArg( + "o", "outputfile", + "A string specifying the name of the file that stores the output image with combined anisotropic weights.", + true, "", "output combined weight image", cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException &e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + using InputImageType = anima::MCMImage; + using OutputImageType = itk::VectorImage; + using InputPixelType = InputImageType::PixelType; + using OutputPixelType = OutputImageType::PixelType; + using MCModelType = anima::MultiCompartmentModel; + using MCModelPointer = MCModelType::Pointer; + + // Read input sample + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(inArg.getValue()); + mcmReader.Update(); + InputImageType::Pointer mcmImage = mcmReader.GetModelVectorImage(); + MCModelPointer mcmPtr = mcmImage->GetDescriptionModel()->Clone(); + InputPixelType mcmValue; + unsigned int numCompartments = mcmPtr->GetNumberOfCompartments(); + unsigned int numIsoCompartments = mcmPtr->GetNumberOfIsotropicCompartments(); + + using InputImageIteratorType = itk::ImageRegionConstIterator; + using OutputImageIteratorType = itk::ImageRegionIterator; + InputImageIteratorType inItr(mcmImage, mcmImage->GetLargestPossibleRegion()); + + // Initialize output image + unsigned int nbOutputComponents = numIsoCompartments + 1; + OutputImageType::Pointer outputImage = OutputImageType::New(); + outputImage->SetRegions(mcmImage->GetLargestPossibleRegion()); + outputImage->CopyInformation(mcmImage); + outputImage->SetVectorLength(nbOutputComponents); + outputImage->Allocate(); + + OutputPixelType outputValue(nbOutputComponents); + outputValue.Fill(0.0); + outputImage->FillBuffer(outputValue); + + std::cout << "- Number of compartments in input image: " << numCompartments << std::endl; + std::cout << "- Number of compartments in output image: " << nbOutputComponents << std::endl; + + OutputImageIteratorType outItr(outputImage, outputImage->GetLargestPossibleRegion()); + + while (!outItr.IsAtEnd()) + { + mcmValue = inItr.Get(); + + bool backgroundVoxel = true; + for (unsigned int i = 0; i < mcmValue.GetNumberOfElements(); ++i) + { + if (mcmValue[i] != 0.0) + { + backgroundVoxel = false; + break; + } + } + + if (backgroundVoxel) + { + ++inItr; + ++outItr; + continue; + } + + mcmPtr->SetModelVector(mcmValue); + + for (unsigned int i = 0; i < numIsoCompartments; ++i) + outputValue[i] = mcmPtr->GetCompartmentWeight(i); + + double anisoWeight = 0.0; + for (unsigned int i = numIsoCompartments; i < numCompartments; ++i) + anisoWeight += mcmPtr->GetCompartmentWeight(i); + + outputValue[numIsoCompartments] = anisoWeight; + + outItr.Set(outputValue); + + ++inItr; + ++outItr; + } + + anima::writeImage(outArg.getValue(), outputImage); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Anima/diffusion/mcm_tools/mcm_orientation_priors/CMakeLists.txt b/Anima/diffusion/mcm_tools/mcm_orientation_priors/CMakeLists.txt new file mode 100644 index 000000000..857e4712f --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_orientation_priors/CMakeLists.txt @@ -0,0 +1,30 @@ +if(BUILD_TOOLS) + + project(animaMCMOrientationPriors) + + # ############################################################################ + # List Sources + # ############################################################################ + + list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) + + # ############################################################################ + # add executable + # ############################################################################ + + add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) + + # ############################################################################ + # Link + # ############################################################################ + + target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} ${TinyXML2_LIBRARY} + AnimaMCM AnimaStatisticalDistributions) + + # ############################################################################ + # install + # ############################################################################ + + set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriors.cxx b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriors.cxx new file mode 100644 index 000000000..da54ff576 --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriors.cxx @@ -0,0 +1,133 @@ +#include +#include +#include + +#include + +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg( + "i", "input-mcms", + "A text file specifying a list of MCM images in a common geometry.", + true, "", "input MCM images", cmd); + TCLAP::ValueArg outOrientationArg( + "o", "output-orientation", + "A string specifying the basename for the output vector images that will store priors on the orientations.", + true, "", "output orientation priors", cmd); + TCLAP::ValueArg outWeightsArg( + "w", "output-weights", + "A string specifying the filename for the output vector image that will store fractions prior.", + true, "", "output weights priors", cmd); + + TCLAP::ValueArg maskArg( + "m", "input-masks", + "A text file specifying a list of mask images in the same common geometry as the input MCM images (default: none).", + false, "", "input mask images", cmd); + TCLAP::ValueArg meanOrientationsArg( + "", "mean-orientations", + "A string specifying the basename for the output vector images that will store mean orientations.", + false, "", "output mean orientation images", cmd); + TCLAP::ValueArg meanFractionsArg( + "", "mean-fractions", + "A string specifying the filename for the output vector image that will store fractions mean.", + false, "", "output mean fractions image", cmd); + TCLAP::ValueArg nbThreadsArg( + "T", "nthreads", + "An integer value specifying the number of threads to run on (default: all cores).", + false, itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(), "number of threads", cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException &e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + using MainFilterType = anima::MCMOrientationPriorsImageFilter; + MainFilterType::Pointer mainFilter = MainFilterType::New(); + + using MCMReaderType = anima::MCMFileReader; + using MaskImageType = MainFilterType::MaskImageType; + using OutputImageType = MainFilterType::OutputImageType; + + // Load MCM images + std::ifstream inputFile(inArg.getValue().c_str()); + + if (!inputFile.is_open()) + { + std::cerr << "Please provide usable file with input MCMs" << std::endl; + return EXIT_FAILURE; + } + + unsigned int nbOfImages = 0; + + while (!inputFile.eof()) + { + char tmpStr[2048]; + inputFile.getline(tmpStr, 2048); + + if (strcmp(tmpStr, "") == 0) + continue; + + std::cout << "Loading image " << nbOfImages + 1 << ": " << tmpStr << std::endl; + + MCMReaderType mcmReader; + mcmReader.SetFileName(tmpStr); + mcmReader.Update(); + + mainFilter->SetInput(nbOfImages, mcmReader.GetModelVectorImage()); + + nbOfImages++; + } + + std::ifstream masksIn; + if (maskArg.getValue() != "") + masksIn.open(maskArg.getValue()); + + if (masksIn.is_open()) + { + char tmpStr[2048]; + while (!masksIn.eof()) + { + masksIn.getline(tmpStr, 2048); + + if (strcmp(tmpStr, "") == 0) + continue; + + mainFilter->AddMaskImage(anima::readImage(tmpStr)); + } + } + + mainFilter->SetNumberOfWorkUnits(nbThreadsArg.getValue()); + mainFilter->Update(); + + unsigned int numberOfAnisotropicCompartments = mainFilter->GetNumberOfAnisotropicCompartments(); + for (unsigned int i = 0; i < numberOfAnisotropicCompartments; ++i) + { + std::string fileName = outOrientationArg.getValue() + "_" + std::to_string(i) + ".nrrd"; + anima::writeImage(fileName, mainFilter->GetOutput(i)); + } + + anima::writeImage(outWeightsArg.getValue(), mainFilter->GetOutput(numberOfAnisotropicCompartments)); + + if (meanOrientationsArg.getValue() != "") + { + for (unsigned int i = 0; i < numberOfAnisotropicCompartments; ++i) + { + std::string fileName = meanOrientationsArg.getValue() + "_" + std::to_string(i) + ".nrrd"; + anima::writeImage(fileName, mainFilter->GetMeanOrientationImage(i)); + } + } + + if (meanFractionsArg.getValue() != "") + anima::writeImage(meanFractionsArg.getValue(), mainFilter->GetMeanFractionsImage()); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.h b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.h new file mode 100644 index 000000000..c310ffbd1 --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#include +#include + +namespace anima +{ + + template + class MCMOrientationPriorsImageFilter : public itk::ImageToImageFilter, itk::VectorImage> + { + public: + /** Standard class type def */ + + using Self = MCMOrientationPriorsImageFilter; + using InputImageType = anima::MCMImage; + using OutputImageType = itk::VectorImage; + using Superclass = itk::ImageToImageFilter; + using Pointer = itk::SmartPointer; + using ConstPointer = itk::SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods) */ + itkTypeMacro(MCMOrientationPriorsImageFilter, ImageToImageFilter); + + using InputImagePointer = typename InputImageType::ConstPointer; + using InputRegionType = typename InputImageType::RegionType; + + using OutputImagePointer = typename OutputImageType::Pointer; + using OutputPixelType = typename OutputImageType::PixelType; + + using MaskImageType = itk::Image; + using MaskImagePointer = MaskImageType::Pointer; + + // Multi-compartment models typedefs + using MCModelType = anima::MultiCompartmentModel; + using MCModelPointer = MCModelType::Pointer; + + void AddMaskImage(MaskImageType *maskImage) { m_MaskImages.push_back(maskImage); } + unsigned int GetNumberOfAnisotropicCompartments() { return m_NumberOfAnisotropicCompartments; } + OutputImagePointer GetMeanOrientationImage(const unsigned int i) { return m_MeanOrientationImages[i]; } + OutputImagePointer GetMeanFractionsImage() { return m_MeanFractionsImage; } + + protected: + MCMOrientationPriorsImageFilter() + { + m_ReferenceInputModels.clear(); + m_MaskImages.clear(); + m_MeanOrientationImages.clear(); + m_NumberOfAnisotropicCompartments = 0; + } + + virtual ~MCMOrientationPriorsImageFilter() {} + + bool isZero(const itk::VariableLengthVector &value) const + { + for (unsigned int i = 0; i < value.GetNumberOfElements(); ++i) + { + if (value[i] != 0) + return false; + } + + return true; + } + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const InputRegionType ®ion) ITK_OVERRIDE; + + private: + ITK_DISALLOW_COPY_AND_ASSIGN(MCMOrientationPriorsImageFilter); + + std::vector m_ReferenceInputModels; + std::vector m_MaskImages; + std::vector m_MeanOrientationImages; + OutputImagePointer m_MeanFractionsImage; + unsigned int m_NumberOfAnisotropicCompartments; + }; + +} // end namespace anima + +#include "animaMCMOrientationPriorsImageFilter.hxx" diff --git a/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.hxx b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.hxx new file mode 100644 index 000000000..b87930d2c --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_orientation_priors/animaMCMOrientationPriorsImageFilter.hxx @@ -0,0 +1,360 @@ +#pragma once +#include "animaMCMOrientationPriorsImageFilter.h" +#include +#include +#include + +#include +#include + +namespace anima +{ + + template + void + MCMOrientationPriorsImageFilter::GenerateOutputInformation() + { + //------------------------------- + // Creates the additional outputs + //------------------------------- + unsigned int prevNum = this->GetNumberOfOutputs(); + + InputImageType *input = const_cast(this->GetInput(0)); + MCModelPointer tmpMCM = input->GetDescriptionModel(); + unsigned int numIsoCompartments = tmpMCM->GetNumberOfIsotropicCompartments(); + unsigned int numCompartments = tmpMCM->GetNumberOfCompartments(); + m_NumberOfAnisotropicCompartments = numCompartments - numIsoCompartments; + unsigned int numOutputs = m_NumberOfAnisotropicCompartments + 1; + + this->SetNumberOfIndexedOutputs(numOutputs); + + for (unsigned int i = prevNum; i < numOutputs; ++i) + this->SetNthOutput(i, this->MakeOutput(i).GetPointer()); + + m_MeanOrientationImages.resize(m_NumberOfAnisotropicCompartments); + + //--------------------------------------------------- + // Call the superclass' implementation of this method + //--------------------------------------------------- + Superclass::GenerateOutputInformation(); + + //-------------------------------------------------- + // Set length of vectors in the output vector images + //-------------------------------------------------- + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + { + OutputImageType *output = this->GetOutput(i); + output->SetVectorLength(4); + m_MeanOrientationImages[i]->SetVectorLength(3); + } + + OutputImageType *output = this->GetOutput(m_NumberOfAnisotropicCompartments); + output->SetVectorLength(m_NumberOfAnisotropicCompartments); + m_MeanFractionsImage->SetVectorLength(m_NumberOfAnisotropicCompartments); + } + + template + void + MCMOrientationPriorsImageFilter::BeforeThreadedGenerateData() + { + this->Superclass::BeforeThreadedGenerateData(); + + m_ReferenceInputModels.resize(this->GetNumberOfIndexedInputs()); + + InputImageType *input = const_cast(this->GetInput(0)); + MCModelPointer tmpMCM = input->GetDescriptionModel(); + unsigned int numIsoCompartments = tmpMCM->GetNumberOfIsotropicCompartments(); + unsigned int numCompartments = tmpMCM->GetNumberOfCompartments(); + + for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) + { + InputImageType *input = const_cast(this->GetInput(i)); + MCModelPointer model = input->GetDescriptionModel(); + + if (model->GetNumberOfIsotropicCompartments() != numIsoCompartments || model->GetNumberOfCompartments() != numCompartments) + itkExceptionMacro("All input MCM images should have the same compartment structure."); + + m_ReferenceInputModels[i] = model; + } + } + + template + void + MCMOrientationPriorsImageFilter::DynamicThreadedGenerateData(const InputRegionType ®ion) + { + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + unsigned int numOutputs = this->GetNumberOfIndexedOutputs(); + + using InputIteratorType = itk::ImageRegionConstIterator; + std::vector inputIterators(numInputs); + for (unsigned int i = 0; i < numInputs; ++i) + inputIterators[i] = InputIteratorType(this->GetInput(i), region); + + using OutputIteratorType = itk::ImageRegionIterator; + std::vector outputIterators(numOutputs); + for (unsigned int i = 0; i < numOutputs; ++i) + outputIterators[i] = OutputIteratorType(this->GetOutput(i), region); + + std::vector meanOrientationIterators(m_NumberOfAnisotropicCompartments); + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + meanOrientationIterators[i] = OutputIteratorType(m_MeanOrientationImages[i], region); + + OutputIteratorType meanFractionsIterator(m_MeanFractionsImage, region); + + using MaskIteratorType = itk::ImageRegionConstIterator; + std::vector maskIterators; + if (m_MaskImages.size() == numInputs) + { + for (unsigned int i = 0; i < numInputs; ++i) + maskIterators.push_back(MaskIteratorType(m_MaskImages[i], region)); + } + unsigned int numMasks = maskIterators.size(); + + std::vector workInputModels(numInputs); + for (unsigned int i = 0; i < numInputs; ++i) + workInputModels[i] = m_ReferenceInputModels[i]->Clone(); + + using ClusterFilterType = anima::SpectralClusteringFilter; + using OrientationVectorType = itk::Vector; + + ClusterFilterType clusterFilter; + ClusterFilterType::MatrixType squaredDistanceMatrix; + std::vector workAllOrientations(numInputs * m_NumberOfAnisotropicCompartments); + std::vector workInputOrientations; + std::vector workAllWeights(numInputs * m_NumberOfAnisotropicCompartments); + std::vector> workInputWeights; + std::vector workConcentrationParameters; + std::vector usefulInputsForWeights(numInputs, false); + std::vector workAllMemberships(numInputs * m_NumberOfAnisotropicCompartments); + OrientationVectorType workSphericalOrientation, workCartesianOrientation; + workSphericalOrientation[2] = 1.0; + std::vector clusterMembers; + anima::WatsonDistribution watsonDistribution; + anima::DirichletDistribution dirichletDistribution; + + OutputPixelType outputOrientationPrior, outputOrientationValue; + outputOrientationPrior.SetSize(4); + outputOrientationValue.SetSize(3); + OutputPixelType outputWeightValues; + outputWeightValues.SetSize(m_NumberOfAnisotropicCompartments); + + while (!inputIterators[0].IsAtEnd()) + { + unsigned int numUsefulInputs = 0; + for (unsigned int i = 0; i < numInputs; ++i) + { + if (isZero(inputIterators[i].Get())) + continue; + + if (numMasks == numInputs) + { + if (maskIterators[i].Get() == 0) + continue; + } + + workInputModels[i]->SetModelVector(inputIterators[i].Get()); + + ++numUsefulInputs; + } + + if (numUsefulInputs == 0) + { + outputOrientationPrior.Fill(0.0); + outputOrientationValue.Fill(0.0); + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + { + outputIterators[i].Set(outputOrientationPrior); + ++outputIterators[i]; + meanOrientationIterators[i].Set(outputOrientationValue); + ++meanOrientationIterators[i]; + } + + outputWeightValues.Fill(0.0); + outputIterators[m_NumberOfAnisotropicCompartments].Set(outputWeightValues); + ++outputIterators[m_NumberOfAnisotropicCompartments]; + meanFractionsIterator.Set(outputWeightValues); + ++meanFractionsIterator; + + for (unsigned int i = 0; i < numInputs; ++i) + ++inputIterators[i]; + + if (numMasks == numInputs) + { + for (unsigned int i = 0; i < numMasks; ++i) + ++maskIterators[i]; + } + + continue; + } + + // Grab list of all orientations, memberships and weights from all MCM models + for (unsigned int i = 0; i < numInputs; ++i) + { + unsigned int numIsoCompartments = workInputModels[i]->GetNumberOfIsotropicCompartments(); + + double sumOfAnisotropicWeights = 0.0; + for (unsigned int j = 0; j < m_NumberOfAnisotropicCompartments; ++j) + { + workSphericalOrientation[0] = workInputModels[i]->GetCompartment(j + numIsoCompartments)->GetOrientationTheta(); + workSphericalOrientation[1] = workInputModels[i]->GetCompartment(j + numIsoCompartments)->GetOrientationPhi(); + anima::TransformSphericalToCartesianCoordinates(workSphericalOrientation, workCartesianOrientation); + workAllOrientations[i * m_NumberOfAnisotropicCompartments + j] = workCartesianOrientation; + workAllMemberships[i * m_NumberOfAnisotropicCompartments + j] = i; + sumOfAnisotropicWeights += workInputModels[i]->GetCompartmentWeight(j + numIsoCompartments); + } + + for (unsigned int j = 0; j < m_NumberOfAnisotropicCompartments; ++j) + workAllWeights[i * m_NumberOfAnisotropicCompartments + j] = workInputModels[i]->GetCompartmentWeight(j + numIsoCompartments) / sumOfAnisotropicWeights; + } + + // Compute squared distance matrix between orientations + unsigned int numOrientations = workAllOrientations.size(); + squaredDistanceMatrix.set_size(numOrientations, numOrientations); + squaredDistanceMatrix.fill_diagonal(0.0); + for (unsigned int i = 0; i < numOrientations - 1; ++i) + { + workCartesianOrientation = workAllOrientations[i]; + for (unsigned int j = i + 1; j < numOrientations; ++j) + { + double cosValue = anima::ComputeScalarProduct(workAllOrientations[j], workCartesianOrientation); + cosValue = std::abs(cosValue); + if (cosValue > 1.0) + cosValue = 1.0; + double distanceValue = std::acos(cosValue) / M_PI; + squaredDistanceMatrix.put(i, j, distanceValue * distanceValue); + if (j != i) + squaredDistanceMatrix.put(j, i, distanceValue * distanceValue); + } + } + + // Perform clustering based on orientations + clusterFilter.SetInputData(squaredDistanceMatrix); + clusterFilter.SetNbClass(m_NumberOfAnisotropicCompartments); + clusterFilter.SetVerbose(false); + clusterFilter.Update(); + + // Characterize orientations in each cluster by a Watson distribution + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + { + clusterMembers = clusterFilter.GetClassMembers(i); + unsigned int clusterSize = clusterMembers.size(); + workInputOrientations.resize(clusterSize); + for (unsigned int j = 0; j < clusterSize; ++j) + workInputOrientations[j] = workAllOrientations[clusterMembers[j]]; + + watsonDistribution.Fit(workInputOrientations, "mle"); + workCartesianOrientation = watsonDistribution.GetMeanAxis(); + for (unsigned int j = 0; j < 3; ++j) + { + outputOrientationPrior[j] = workCartesianOrientation[j]; + outputOrientationValue[j] = workCartesianOrientation[j]; + } + outputOrientationPrior[3] = watsonDistribution.GetConcentrationParameter(); + outputIterators[i].Set(outputOrientationPrior); + ++outputIterators[i]; + meanOrientationIterators[i].Set(outputOrientationValue); + ++meanOrientationIterators[i]; + } + + // Characterize anisotropic weights in each cluster by a Dirichlet distribution + + // First, find MCMs whose anisotropic compartments did spread in all clusters + std::fill(usefulInputsForWeights.begin(), usefulInputsForWeights.end(), false); + unsigned int nbUsefulInputsForWeights = 0; + for (unsigned int i = 0; i < numInputs; ++i) + { + unsigned int numCompartments = 0; + for (unsigned int j = 0; j < m_NumberOfAnisotropicCompartments; ++j) + { + clusterMembers = clusterFilter.GetClassMembers(j); + for (unsigned int k = 0; k < clusterMembers.size(); ++k) + { + if (workAllMemberships[clusterMembers[k]] == i) + { + numCompartments++; + break; + } + } + } + + if (numCompartments == m_NumberOfAnisotropicCompartments) + { + usefulInputsForWeights[i] = true; + nbUsefulInputsForWeights++; + } + } + + // Next, compute Dirichlet priors in each cluster + if (nbUsefulInputsForWeights < 2) + { + outputWeightValues.Fill(1.0); + outputIterators[m_NumberOfAnisotropicCompartments].Set(outputWeightValues); + ++outputIterators[m_NumberOfAnisotropicCompartments]; + meanFractionsIterator.Set(outputWeightValues); + ++meanFractionsIterator; + + for (unsigned int i = 0; i < numInputs; ++i) + ++inputIterators[i]; + + if (numMasks == numInputs) + { + for (unsigned int i = 0; i < numMasks; ++i) + ++maskIterators[i]; + } + + continue; + } + + workInputWeights.resize(nbUsefulInputsForWeights); + unsigned int pos = 0; + for (unsigned int i = 0; i < numInputs; ++i) + { + if (!usefulInputsForWeights[i]) + continue; + + workInputWeights[pos].resize(m_NumberOfAnisotropicCompartments); + + for (unsigned int j = 0; j < m_NumberOfAnisotropicCompartments; ++j) + { + clusterMembers = clusterFilter.GetClassMembers(j); + for (unsigned int k = 0; k < clusterMembers.size(); ++k) + { + if (workAllMemberships[clusterMembers[k]] == i) + { + workInputWeights[pos][j] = workAllWeights[clusterMembers[k]]; + break; + } + } + } + + pos++; + } + + dirichletDistribution.Fit(workInputWeights, "mle"); + + workConcentrationParameters = dirichletDistribution.GetConcentrationParameters(); + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + outputWeightValues[i] = workConcentrationParameters[i]; + + outputIterators[m_NumberOfAnisotropicCompartments].Set(outputWeightValues); + ++outputIterators[m_NumberOfAnisotropicCompartments]; + + workConcentrationParameters = dirichletDistribution.GetMean(); + for (unsigned int i = 0; i < m_NumberOfAnisotropicCompartments; ++i) + outputWeightValues[i] = workConcentrationParameters[i]; + + meanFractionsIterator.Set(outputWeightValues); + ++meanFractionsIterator; + + for (unsigned int i = 0; i < numInputs; ++i) + ++inputIterators[i]; + + if (numMasks == numInputs) + { + for (unsigned int i = 0; i < numMasks; ++i) + ++maskIterators[i]; + } + } + } + +} // end namespace anima diff --git a/Anima/diffusion/tractography/CMakeLists.txt b/Anima/diffusion/tractography/CMakeLists.txt index 89db5f87a..026a26209 100644 --- a/Anima/diffusion/tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries(${PROJECT_NAME} AnimaDataIO AnimaOptimizers AnimaSHTools + AnimaStatisticalDistributions AnimaMCM AnimaMCMBase ) diff --git a/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.h b/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.h index d7fdea3c3..3755e39ff 100644 --- a/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.h +++ b/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -221,6 +223,12 @@ class BaseProbabilisticTractographyImageFilter : public itk::ProcessObject //! Computes additional scalar maps that are model dependent to add to the output virtual void ComputeAdditionalScalarMaps() {} + //! Holds an object of class WatsonDistribution for sampling new directions + anima::WatsonDistribution m_WatsonDistribution; + + //! Holds a sample of size 1 of directions + DirectionVectorType m_SampleOfDirections; + private: ITK_DISALLOW_COPY_AND_ASSIGN(BaseProbabilisticTractographyImageFilter); diff --git a/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.hxx b/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.hxx index 0e584e38a..48f34f17d 100644 --- a/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.hxx +++ b/Anima/diffusion/tractography/animaBaseProbabilisticTractographyImageFilter.hxx @@ -58,6 +58,8 @@ BaseProbabilisticTractographyImageFilter m_HighestProcessedSeed = 0; m_ProgressReport = 0; + + m_SampleOfDirections.resize(1); } template diff --git a/Anima/diffusion/tractography/animaDTIProbabilisticTractographyImageFilter.cxx b/Anima/diffusion/tractography/animaDTIProbabilisticTractographyImageFilter.cxx index 426c58929..add35c464 100644 --- a/Anima/diffusion/tractography/animaDTIProbabilisticTractographyImageFilter.cxx +++ b/Anima/diffusion/tractography/animaDTIProbabilisticTractographyImageFilter.cxx @@ -2,9 +2,6 @@ #include #include -#include -#include -#include #include #include @@ -58,13 +55,11 @@ DTIProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi sampling_direction = oldDirection; concentrationParameter = this->GetKappaOfPriorDistribution(); } - -// if (concentrationParameter > 700) -// anima::SampleFromVMFDistributionNumericallyStable(concentrationParameter,sampling_direction,resVec,random_generator); -// else -// anima::SampleFromVMFDistribution(concentrationParameter,sampling_direction,resVec,random_generator); - anima::SampleFromWatsonDistribution(concentrationParameter,sampling_direction,resVec,3,random_generator); + m_WatsonDistribution.SetMeanAxis(sampling_direction); + m_WatsonDistribution.SetConcentrationParameter(concentrationParameter); + m_WatsonDistribution.Random(m_SampleOfDirections, random_generator); + resVec = m_SampleOfDirections[0]; if (is2d) { @@ -74,11 +69,13 @@ DTIProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi if (LC > m_ThresholdForProlateTensor) { -// log_prior = anima::safe_log(anima::ComputeVMFPdf(resVec, oldDirection, this->GetKappaOfPriorDistribution())); - log_prior = anima::safe_log(anima::EvaluateWatsonPDF(resVec, oldDirection, this->GetKappaOfPriorDistribution())); + m_WatsonDistribution.SetMeanAxis(oldDirection); + m_WatsonDistribution.SetConcentrationParameter(this->GetKappaOfPriorDistribution()); + log_prior = m_WatsonDistribution.GetLogDensity(resVec); -// log_proposal = anima::safe_log(anima::ComputeVMFPdf(resVec, sampling_direction, concentrationParameter)); - log_proposal = anima::safe_log(anima::EvaluateWatsonPDF(resVec, sampling_direction, concentrationParameter)); + m_WatsonDistribution.SetMeanAxis(sampling_direction); + m_WatsonDistribution.SetConcentrationParameter(concentrationParameter); + log_proposal = m_WatsonDistribution.GetLogDensity(resVec); } if (anima::ComputeScalarProduct(oldDirection, resVec) < 0) @@ -199,19 +196,23 @@ double DTIProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0 if (noiseValue > 0) concentrationParameter = b0Value / std::sqrt(noiseValue); + m_WatsonDistribution.SetMeanAxis(newDirection); + if (LC > m_ThresholdForProlateTensor) { Vector3DType dtiPrincipalDirection(0.0); this->GetDTIPrincipalDirection(modelValue, dtiPrincipalDirection, is2d); - logLikelihood = std::log(anima::EvaluateWatsonPDF(dtiPrincipalDirection, newDirection, concentrationParameter)); + m_WatsonDistribution.SetConcentrationParameter(concentrationParameter); + logLikelihood = m_WatsonDistribution.GetLogDensity(dtiPrincipalDirection); } else { Vector3DType dtiMinorDirection(0.0); this->GetDTIMinorDirection(modelValue, dtiMinorDirection); - logLikelihood = std::log(anima::EvaluateWatsonPDF(dtiMinorDirection, newDirection, - concentrationParameter)); + m_WatsonDistribution.SetConcentrationParameter(-concentrationParameter); + logLikelihood = m_WatsonDistribution.GetLogDensity(dtiMinorDirection); } double resVal = log_prior + logLikelihood - log_proposal; diff --git a/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx index a2f9c0c7a..208059216 100644 --- a/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx +++ b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include #include @@ -117,12 +115,11 @@ MCMProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi itkExceptionMacro("Null old direction, we're doomed"); Vector3DType resVec; - // if (chosenKappa > 700) - // anima::SampleFromVMFDistributionNumericallyStable(chosenKappa,sampling_direction,resVec,random_generator); - // else - // anima::SampleFromVMFDistribution(chosenKappa,sampling_direction,resVec,random_generator); - anima::SampleFromWatsonDistribution(chosenKappa,sampling_direction,resVec,3,random_generator); + m_WatsonDistribution.SetMeanAxis(sampling_direction); + m_WatsonDistribution.SetConcentrationParameter(chosenKappa); + m_WatsonDistribution.Random(m_SampleOfDirections, random_generator); + resVec = m_SampleOfDirections[0]; if (is2d) { @@ -132,15 +129,17 @@ MCMProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi if (effectiveNumDirs > 0) { - // log_prior = anima::safe_log(anima::ComputeVMFPdf(resVec, oldDirection, this->GetKappaOfPriorDistribution())); - log_prior = anima::safe_log(anima::EvaluateWatsonPDF(resVec, oldDirection, this->GetKappaOfPriorDistribution())); + m_WatsonDistribution.SetMeanAxis(oldDirection); + m_WatsonDistribution.SetConcentrationParameter(this->GetKappaOfPriorDistribution()); + log_prior = m_WatsonDistribution.GetLogDensity(resVec); log_proposal = 0; double sumWeights = 0; for (unsigned int i = 0;i < effectiveNumDirs;++i) { - // log_proposal += mixtureWeights[i] * anima::ComputeVMFPdf(resVec, maximaMCM[i], kappaValues[i]); - log_proposal += mixtureWeights[i] * anima::EvaluateWatsonPDF(resVec, maximaMCM[i], kappaValues[i]); + m_WatsonDistribution.SetMeanAxis(maximaMCM[i]); + m_WatsonDistribution.SetConcentrationParameter(kappaValues[i]); + log_proposal += mixtureWeights[i] * m_WatsonDistribution.GetDensity(resVec); sumWeights += mixtureWeights[i]; } @@ -290,6 +289,9 @@ double MCMProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0 double concentrationParameter = b0Value / std::sqrt(noiseValue); + m_WatsonDistribution.SetMeanAxis(newDirection); + m_WatsonDistribution.SetConcentrationParameter(concentrationParameter); + bool oneTested = false; for (unsigned int i = workModel->GetNumberOfIsotropicCompartments();i < workModel->GetNumberOfCompartments();++i) { @@ -306,7 +308,7 @@ double MCMProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0 anima::Normalize(tmpVec,tmpVec); } - double tmpVal = std::log(anima::EvaluateWatsonPDF(tmpVec, newDirection, concentrationParameter)); + double tmpVal = m_WatsonDistribution.GetLogDensity(tmpVec); if ((tmpVal > logLikelihood)||(!oneTested)) { logLikelihood = tmpVal; diff --git a/Anima/diffusion/tractography/animaODFProbabilisticTractographyImageFilter.cxx b/Anima/diffusion/tractography/animaODFProbabilisticTractographyImageFilter.cxx index 00ba94562..4513c5de3 100644 --- a/Anima/diffusion/tractography/animaODFProbabilisticTractographyImageFilter.cxx +++ b/Anima/diffusion/tractography/animaODFProbabilisticTractographyImageFilter.cxx @@ -7,8 +7,6 @@ #include #include -#include -#include #include namespace anima @@ -104,12 +102,10 @@ ODFProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi chosenKappa = kappaValues[chosenDirection]; } - // if (chosenKappa > 700) - // anima::SampleFromVMFDistributionNumericallyStable(chosenKappa,sampling_direction,resVec,random_generator); - // else - // anima::SampleFromVMFDistribution(chosenKappa,sampling_direction,resVec,random_generator); - - anima::SampleFromWatsonDistribution(chosenKappa,sampling_direction,resVec,3,random_generator); + m_WatsonDistribution.SetMeanAxis(sampling_direction); + m_WatsonDistribution.SetConcentrationParameter(chosenKappa); + m_WatsonDistribution.Random(m_SampleOfDirections, random_generator); + resVec = m_SampleOfDirections[0]; if (is2d) { @@ -119,14 +115,16 @@ ODFProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDi if (numDirs > 0) { - // log_prior = anima::safe_log( anima::ComputeVMFPdf(resVec, oldDirection, this->GetKappaOfPriorDistribution())); - log_prior = anima::safe_log( anima::EvaluateWatsonPDF(resVec, oldDirection, this->GetKappaOfPriorDistribution())); + m_WatsonDistribution.SetMeanAxis(oldDirection); + m_WatsonDistribution.SetConcentrationParameter(this->GetKappaOfPriorDistribution()); + log_prior = m_WatsonDistribution.GetLogDensity(resVec); log_proposal = 0; for (unsigned int i = 0;i < numDirs;++i) { - // log_proposal += mixtureWeights[i] * anima::ComputeVMFPdf(resVec, maximaODF[i], kappaValues[i]); - log_proposal += mixtureWeights[i] * anima::EvaluateWatsonPDF(resVec, maximaODF[i], kappaValues[i]); + m_WatsonDistribution.SetMeanAxis(maximaODF[i]); + m_WatsonDistribution.SetConcentrationParameter(kappaValues[i]); + log_proposal += mixtureWeights[i] * m_WatsonDistribution.GetDensity(resVec); } log_proposal = anima::safe_log(log_proposal); @@ -234,9 +232,12 @@ double ODFProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0 double concentrationParameter = b0Value / std::sqrt(noiseValue); + m_WatsonDistribution.SetMeanAxis(newDirection); + m_WatsonDistribution.SetConcentrationParameter(concentrationParameter); + for (unsigned int i = 0;i < numDirs;++i) { - double tmpVal = std::log(anima::EvaluateWatsonPDF(maximaODF[i], newDirection, concentrationParameter)); + double tmpVal = m_WatsonDistribution.GetLogDensity(maximaODF[i]); if ((tmpVal > logLikelihood)||(i == 0)) logLikelihood = tmpVal; } diff --git a/Anima/filtering/noise_generator/animaNoiseGenerator.cxx b/Anima/filtering/noise_generator/animaNoiseGenerator.cxx index f6dcf3ef9..f01e1263e 100644 --- a/Anima/filtering/noise_generator/animaNoiseGenerator.cxx +++ b/Anima/filtering/noise_generator/animaNoiseGenerator.cxx @@ -13,13 +13,12 @@ struct arguments }; template -void -addNoise(itk::ImageIOBase::Pointer imageIO, const arguments &args) +void addNoise(itk::ImageIOBase::Pointer imageIO, const arguments &args) { - typedef itk::Image InputImageType; + typedef itk::Image InputImageType; typedef anima::NoiseGeneratorImageFilter NoiseFilterType; typedef typename NoiseFilterType::OutputImageType OutputImageType; - + typename NoiseFilterType::Pointer mainFilter = NoiseFilterType::New(); mainFilter->SetNumberOfWorkUnits(args.nthreads); mainFilter->SetInput(anima::readImage(args.input)); @@ -27,7 +26,7 @@ addNoise(itk::ImageIOBase::Pointer imageIO, const arguments &args) mainFilter->SetUseGaussianDistribution(args.gaussianNoise); mainFilter->SetNumberOfReplicates(args.nreplicates); mainFilter->Update(); - + if (args.nreplicates == 1) anima::writeImage(args.output, mainFilter->GetOutput(0)); else @@ -42,8 +41,8 @@ addNoise(itk::ImageIOBase::Pointer imageIO, const arguments &args) fileExtension = filePrefix.substr(pointLocation) + ".gz"; filePrefix = filePrefix.substr(0, pointLocation); } - - for (unsigned int i = 0;i < args.nreplicates;++i) + + for (unsigned int i = 0; i < args.nreplicates; ++i) { std::stringstream ss; ss << std::setw(3) << std::setfill('0') << (i + 1); @@ -53,36 +52,35 @@ addNoise(itk::ImageIOBase::Pointer imageIO, const arguments &args) } } -template -void -retrieveNbDimensions(itk::ImageIOBase::Pointer imageIO, const arguments &args) +template +void retrieveNbDimensions(itk::ImageIOBase::Pointer imageIO, const arguments &args) { ANIMA_RETRIEVE_NUMBER_OF_DIMENSIONS(imageIO, ComponentType, addNoise, imageIO, args) } int main(int argc, char **argv) { - TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); - - TCLAP::ValueArg inArg("i","inputfile","Input image on which to add noise.",true,"","input image",cmd); - TCLAP::ValueArg outArg("o","outputfile","Output image. If number of replicates is 1, it is used as provided for saving the noisy dataset with the corresponding file name. If number of replicates > 1, the extension is extracted as desired output file format and the file name without extension is used as base file name for all replicates.",true,"","output file prefix",cmd); - TCLAP::ValueArg refArg("r","reference","Masked reference image over which the average SNR is defined.",true,"","reference image",cmd); - - TCLAP::ValueArg snrArg("s","snr","Average SNR over the reference image (default: 25dB). See also -L option for the unit",false,25.0,"mean snr",cmd); - TCLAP::ValueArg repArg("n","num-replicates","Number of independent noisy datasets to generate (default: 1).",false,1,"number of replicates",cmd); - - TCLAP::SwitchArg linearSNRArg("L","linear-snr","SNR is given directly in linear form and not in dB (default).",cmd,false); - TCLAP::SwitchArg gaussArg("G","gauss-noise","Adds Gaussian noise instead of Rician noise.",cmd,false); - TCLAP::SwitchArg matchArg("M","match-snr","Make Gaussian noise comparable to Rician noise in terms of SNR.",cmd,false); - TCLAP::SwitchArg verboseArg("V","verbose","Outputs noise calculations to the console.",cmd,false); - - TCLAP::ValueArg nbpArg("p","numberofthreads","Number of threads to run on (default : all cores).",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"number of threads",cmd); + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg("i", "inputfile", "Input image on which to add noise.", true, "", "input image", cmd); + TCLAP::ValueArg outArg("o", "outputfile", "Output image. If number of replicates is 1, it is used as provided for saving the noisy dataset with the corresponding file name. If number of replicates > 1, the extension is extracted as desired output file format and the file name without extension is used as base file name for all replicates.", true, "", "output file prefix", cmd); + TCLAP::ValueArg refArg("r", "reference", "Masked reference image over which the average SNR is defined.", true, "", "reference image", cmd); + + TCLAP::ValueArg snrArg("s", "snr", "Average SNR over the reference image (default: 25dB). See also -L option for the unit", false, 25.0, "mean snr", cmd); + TCLAP::ValueArg repArg("n", "num-replicates", "Number of independent noisy datasets to generate (default: 1).", false, 1, "number of replicates", cmd); + + TCLAP::SwitchArg linearSNRArg("L", "linear-snr", "SNR is given directly in linear form and not in dB (default).", cmd, false); + TCLAP::SwitchArg gaussArg("G", "gauss-noise", "Adds Gaussian noise instead of Rician noise.", cmd, false); + TCLAP::SwitchArg matchArg("M", "match-snr", "Make Gaussian noise comparable to Rician noise in terms of SNR.", cmd, false); + TCLAP::SwitchArg verboseArg("V", "verbose", "Outputs noise calculations to the console.", cmd, false); + + TCLAP::ValueArg nbpArg("p", "numberofthreads", "Number of threads to run on (default : all cores).", false, itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(), "number of threads", cmd); try { - cmd.parse(argc,argv); + cmd.parse(argc, argv); } - catch (TCLAP::ArgException& e) + catch (TCLAP::ArgException &e) { std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; return EXIT_FAILURE; @@ -92,36 +90,36 @@ int main(int argc, char **argv) double snr = snrArg.getValue(); if (!linearSNRArg.isSet()) snr = std::pow(10.0, snr / 20.0); - + // Find average signal value in reference image - typedef itk::Image Input3DImageType; + typedef itk::Image Input3DImageType; typedef itk::ImageRegionConstIterator Input3DIteratorType; - + Input3DImageType::Pointer referenceImage = anima::readImage(refArg.getValue()); Input3DIteratorType refItr(referenceImage, referenceImage->GetLargestPossibleRegion()); - + double meanReferenceValue = 0; unsigned int numNonZeroVoxels = 0; - + while (!refItr.IsAtEnd()) { double referenceValue = refItr.Get(); - + if (referenceValue == 0) { ++refItr; continue; } - + meanReferenceValue *= (numNonZeroVoxels / (numNonZeroVoxels + 1.0)); meanReferenceValue += referenceValue / (numNonZeroVoxels + 1.0); ++numNonZeroVoxels; ++refItr; } - + // Retrieve Rice sigma parameter double riceSigmaParameter = meanReferenceValue / snr; - + // Compute mean and variance from Rice distribution double snrValue = meanReferenceValue / riceSigmaParameter; double laguerreArgument = -1.0 * snrValue * snrValue / 2.0; @@ -134,37 +132,37 @@ int main(int argc, char **argv) return EXIT_FAILURE; } riceStdValue = std::sqrt(riceStdValue); - + // Deduce effective SNR double effSnrValue = riceMeanValue / riceStdValue; - + // Compute Gaussian sigma parameter double gaussSigmaParameter = (matchArg.isSet()) ? meanReferenceValue / effSnrValue : riceSigmaParameter; - + // Set noise standard deviation double noiseSigmaParameter = (gaussArg.isSet()) ? gaussSigmaParameter : riceSigmaParameter; - + if (verboseArg.isSet()) { // Output some information about the noise structure in the console std::cout << " - User-defined average SNR: " << snrArg.getValue() << " dB." << std::endl; - std::cout << " - Reference image used for noise variance calculation: " << refArg.getValue() << std:: endl; + std::cout << " - Reference image used for noise variance calculation: " << refArg.getValue() << std::endl; std::cout << " - Average signal in foreground voxels of the reference image: " << meanReferenceValue << std::endl; std::cout << " - Resulting coil standard deviation (Rice sigma parameter): " << riceSigmaParameter << std::endl; std::cout << " - Mean value of Rice distribution: " << riceMeanValue << std::endl; std::cout << " - Standard deviation of Rice distribution: " << riceStdValue << std::endl; std::cout << " - Effective average SNR: " << 20.0 * std::log10(effSnrValue) << " dB." << std::endl; - + if (gaussArg.isSet()) std::cout << " - Noise distribution: Gaussian" << std::endl; else std::cout << " - Noise distribution: Rician" << std::endl; - + std::cout << " - Resulting noise sigma parameter: " << noiseSigmaParameter << std::endl; } // Find out the type of the image in file - itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(inArg.getValue().c_str(),itk::IOFileModeEnum::ReadMode); + itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(inArg.getValue().c_str(), itk::IOFileModeEnum::ReadMode); if (!imageIO) { @@ -175,16 +173,16 @@ int main(int argc, char **argv) // Now that we found the appropriate ImageIO class, ask it to read the meta data from the image file. imageIO->SetFileName(inArg.getValue()); imageIO->ReadImageInformation(); - + arguments args; - + args.input = inArg.getValue(); args.output = outArg.getValue(); args.sigma = noiseSigmaParameter; args.nreplicates = repArg.getValue(); args.nthreads = nbpArg.getValue(); args.gaussianNoise = gaussArg.isSet(); - + try { ANIMA_RETRIEVE_COMPONENT_TYPE(imageIO, retrieveNbDimensions, imageIO, args); @@ -194,6 +192,6 @@ int main(int argc, char **argv) std::cerr << err << std::endl; return EXIT_FAILURE; } - + return EXIT_SUCCESS; } diff --git a/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.h b/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.h index 7053f698a..18f9e611b 100644 --- a/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.h +++ b/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.h @@ -9,71 +9,69 @@ namespace anima { - -template -class NoiseGeneratorImageFilter : -public anima::NumberedThreadImageToImageFilter > -{ -public: - /** Standard class typedefs. */ - typedef NoiseGeneratorImageFilter Self; - typedef ImageType TInputImage; - typedef itk::Image TOutputImage; - typedef anima::NumberedThreadImageToImageFilter Superclass; - typedef itk::SmartPointer Pointer; - typedef itk::SmartPointer ConstPointer; - - /** Method for creation through the object factory. */ - itkNewMacro(Self) - - /** Run-time type information (and related methods) */ - itkTypeMacro(NoiseGeneratorImageFilter, anima::NumberedThreadImageToImageFilter) - - typedef typename TOutputImage::PixelType OutputPixelType; - typedef typename TInputImage::PixelType InputPixelType; - - /** Image typedef support */ - typedef TInputImage InputImageType; - typedef TOutputImage OutputImageType; - typedef typename InputImageType::Pointer InputImagePointer; - typedef typename OutputImageType::Pointer OutputImagePointer; - - /** Superclass typedefs. */ - typedef typename Superclass::OutputImageRegionType OutputImageRegionType; - - itkSetMacro(NumberOfReplicates, unsigned int) - itkGetConstMacro(NumberOfReplicates, unsigned int) - - itkSetMacro(NoiseSigma, double) - itkGetConstMacro(NoiseSigma, double) - - itkSetMacro(UseGaussianDistribution, bool) - itkGetConstMacro(UseGaussianDistribution, bool) - -protected: - NoiseGeneratorImageFilter() - { - m_NumberOfReplicates = 1; - m_NoiseSigma = 1.0; - m_UseGaussianDistribution = false; - } - - virtual ~NoiseGeneratorImageFilter() + template + class NoiseGeneratorImageFilter : public anima::NumberedThreadImageToImageFilter> { - } - - void GenerateOutputInformation() ITK_OVERRIDE; - void BeforeThreadedGenerateData() ITK_OVERRIDE; - void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; - -private: - ITK_DISALLOW_COPY_AND_ASSIGN(NoiseGeneratorImageFilter); - - unsigned int m_NumberOfReplicates; - double m_NoiseSigma; - bool m_UseGaussianDistribution; - std::vector m_Generators; -}; + public: + /** Standard class typedefs. */ + typedef NoiseGeneratorImageFilter Self; + typedef ImageType TInputImage; + typedef itk::Image TOutputImage; + typedef anima::NumberedThreadImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods) */ + itkTypeMacro(NoiseGeneratorImageFilter, anima::NumberedThreadImageToImageFilter); + + typedef typename TOutputImage::PixelType OutputPixelType; + typedef typename TInputImage::PixelType InputPixelType; + + /** Image typedef support */ + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + itkSetMacro(NumberOfReplicates, unsigned int); + itkGetConstMacro(NumberOfReplicates, unsigned int); + + itkSetMacro(NoiseSigma, double); + itkGetConstMacro(NoiseSigma, double); + + itkSetMacro(UseGaussianDistribution, bool); + itkGetConstMacro(UseGaussianDistribution, bool); + + protected: + NoiseGeneratorImageFilter() + { + m_NumberOfReplicates = 1; + m_NoiseSigma = 1.0; + m_UseGaussianDistribution = false; + } + + virtual ~NoiseGeneratorImageFilter() + { + } + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + private: + ITK_DISALLOW_COPY_AND_ASSIGN(NoiseGeneratorImageFilter); + + unsigned int m_NumberOfReplicates; + double m_NoiseSigma; + bool m_UseGaussianDistribution; + std::vector m_Generators; + }; } // end namespace anima diff --git a/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.hxx b/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.hxx index e6af063d1..458983567 100644 --- a/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.hxx +++ b/Anima/filtering/noise_generator/animaNoiseGeneratorImageFilter.hxx @@ -1,7 +1,6 @@ #pragma once #include "animaNoiseGeneratorImageFilter.h" -#include #include #include @@ -9,84 +8,83 @@ namespace anima { -template -void -NoiseGeneratorImageFilter -::GenerateOutputInformation () -{ - //------------------------------- - // Creates the additional outputs - //------------------------------- - unsigned int prevNum = this->GetNumberOfOutputs(); - - this->SetNumberOfIndexedOutputs(m_NumberOfReplicates); - - for (unsigned int i = prevNum;i < m_NumberOfReplicates;++i) + template + void + NoiseGeneratorImageFilter::GenerateOutputInformation() { - this->SetNthOutput(i,this->MakeOutput(i).GetPointer()); + //------------------------------- + // Creates the additional outputs + //------------------------------- + unsigned int prevNum = this->GetNumberOfOutputs(); + + this->SetNumberOfIndexedOutputs(m_NumberOfReplicates); + + for (unsigned int i = prevNum; i < m_NumberOfReplicates; ++i) + { + this->SetNthOutput(i, this->MakeOutput(i).GetPointer()); + } + + //--------------------------------------------------- + // Call the superclass' implementation of this method + //--------------------------------------------------- + Superclass::GenerateOutputInformation(); } - - //--------------------------------------------------- - // Call the superclass' implementation of this method - //--------------------------------------------------- - Superclass::GenerateOutputInformation(); -} - -template -void -NoiseGeneratorImageFilter -::BeforeThreadedGenerateData () -{ - Superclass::BeforeThreadedGenerateData(); - - m_Generators.clear(); - std::mt19937 motherGenerator(time(ITK_NULLPTR)); - for (int i = 0;i < this->GetNumberOfWorkUnits();++i) - m_Generators.push_back(std::mt19937(motherGenerator())); -} - -template -void -NoiseGeneratorImageFilter -::DynamicThreadedGenerateData (const OutputImageRegionType &outputRegionForThread) -{ - typedef itk::ImageRegionConstIterator InputImageIteratorType; - typedef itk::ImageRegionIterator OutputImageIteratorType; - - InputImageIteratorType inputIterator(this->GetInput(), outputRegionForThread); - - std::vector outIterators(m_NumberOfReplicates); - for (unsigned int i = 0;i < m_NumberOfReplicates;++i) - outIterators[i] = OutputImageIteratorType(this->GetOutput(i), outputRegionForThread); - - unsigned int threadId = this->GetSafeThreadId(); - - while (!inputIterator.IsAtEnd()) + + template + void + NoiseGeneratorImageFilter::BeforeThreadedGenerateData() + { + Superclass::BeforeThreadedGenerateData(); + + m_Generators.clear(); + std::mt19937 motherGenerator(time(ITK_NULLPTR)); + for (int i = 0; i < this->GetNumberOfWorkUnits(); ++i) + m_Generators.push_back(std::mt19937(motherGenerator())); + } + + template + void + NoiseGeneratorImageFilter::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) { - double refData = inputIterator.Get(); - - for (unsigned int i = 0;i < m_NumberOfReplicates;++i) + typedef itk::ImageRegionConstIterator InputImageIteratorType; + typedef itk::ImageRegionIterator OutputImageIteratorType; + + InputImageIteratorType inputIterator(this->GetInput(), outputRegionForThread); + + std::vector outIterators(m_NumberOfReplicates); + for (unsigned int i = 0; i < m_NumberOfReplicates; ++i) + outIterators[i] = OutputImageIteratorType(this->GetOutput(i), outputRegionForThread); + + unsigned int threadId = this->GetSafeThreadId(); + + std::normal_distribution normDistr(0.0, m_NoiseSigma); + + while (!inputIterator.IsAtEnd()) { - double realNoise = SampleFromGaussianDistribution(0.0, m_NoiseSigma, m_Generators[threadId]); - double data = refData + realNoise; - - if (!m_UseGaussianDistribution) + double refData = inputIterator.Get(); + + for (unsigned int i = 0; i < m_NumberOfReplicates; ++i) { - double imagNoise = SampleFromGaussianDistribution(0.0, m_NoiseSigma, m_Generators[threadId]); - data = std::sqrt(data * data + imagNoise * imagNoise); + double realNoise = normDistr(m_Generators[threadId]); + double data = refData + realNoise; + + if (!m_UseGaussianDistribution) + { + double imagNoise = normDistr(m_Generators[threadId]); + data = std::sqrt(data * data + imagNoise * imagNoise); + } + + if ((std::isnan(data)) || (!std::isfinite(data))) + data = refData; + + outIterators[i].Set(data); + ++outIterators[i]; } - - if ((std::isnan(data)) || (!std::isfinite(data))) - data = refData; - - outIterators[i].Set(data); - ++outIterators[i]; + + ++inputIterator; } - - ++inputIterator; - } - this->SafeReleaseThreadId(threadId); -} + this->SafeReleaseThreadId(threadId); + } -} //end of namespace anima +} // end of namespace anima diff --git a/Anima/math-tools/CMakeLists.txt b/Anima/math-tools/CMakeLists.txt index 1ba6c9799..4bebde674 100644 --- a/Anima/math-tools/CMakeLists.txt +++ b/Anima/math-tools/CMakeLists.txt @@ -1,13 +1,13 @@ project(ANIMA-MATHS) -################################################################################ +# ############################################################################## # Here go the add_subdirectories, no code should be at the root of the project -################################################################################ +# ############################################################################## add_subdirectory(arithmetic) add_subdirectory(common_tools) -if (USE_VTK AND VTK_FOUND) +if(USE_VTK AND VTK_FOUND) add_subdirectory(data_io) endif() @@ -21,7 +21,9 @@ add_subdirectory(statistics) add_subdirectory(statistical_distributions) add_subdirectory(statistical_tests) -if (BUILD_TESTING) +if(BUILD_TESTING) add_subdirectory(matrix_operations/qr_test) + add_subdirectory(special_functions/dawson_integral_test) + add_subdirectory(special_functions/kummer_function_test) add_subdirectory(statistical_distributions/watson_sh_test) -endif() \ No newline at end of file +endif() diff --git a/Anima/math-tools/matrix_operations/animaVectorOperations.hxx b/Anima/math-tools/matrix_operations/animaVectorOperations.hxx index 12ea16eae..203b7c442 100644 --- a/Anima/math-tools/matrix_operations/animaVectorOperations.hxx +++ b/Anima/math-tools/matrix_operations/animaVectorOperations.hxx @@ -1,11 +1,12 @@ #include "animaVectorOperations.h" -#include - #include #include +#include + #include +#include #include namespace anima diff --git a/Anima/math-tools/special_functions/CMakeLists.txt b/Anima/math-tools/special_functions/CMakeLists.txt index 8b6c729bc..b7f2c8cfe 100644 --- a/Anima/math-tools/special_functions/CMakeLists.txt +++ b/Anima/math-tools/special_functions/CMakeLists.txt @@ -1,44 +1,33 @@ project(AnimaSpecialFunctions) -## ############################################################################# -## List Sources -## ############################################################################# +# ############################################################################## +# List Sources +# ############################################################################## -list_source_files(${PROJECT_NAME} - ${CMAKE_CURRENT_SOURCE_DIR} - ) +list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) +# ############################################################################## +# add lib +# ############################################################################## -## ############################################################################# -## add lib -## ############################################################################# +add_library(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) -add_library(${PROJECT_NAME} - ${${PROJECT_NAME}_CFILES} - ) +# ############################################################################## +# Link +# ############################################################################## -## ############################################################################# -## Link -## ############################################################################# +target_link_libraries(${PROJECT_NAME} ITKCommon) -target_link_libraries(${PROJECT_NAME} - ITKCommon - ) - - -################################################################################ +# ############################################################################## # Auto generate the export file for the libs -################################################################################ +# ############################################################################## -generate_export_header(${PROJECT_NAME} - STATIC_DEFINE ${PROJECT_NAME}_BUILT_AS_STATIC - EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/exports/${PROJECT_NAME}Export.h" - ) +generate_export_header( + ${PROJECT_NAME} STATIC_DEFINE ${PROJECT_NAME}_BUILT_AS_STATIC + EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/exports/${PROJECT_NAME}Export.h") - -## ############################################################################# -## install -## ############################################################################# +# ############################################################################## +# install +# ############################################################################## set_lib_install_rules(${PROJECT_NAME}) - diff --git a/Anima/math-tools/special_functions/animaBesselFunctions.cxx b/Anima/math-tools/special_functions/animaBesselFunctions.cxx index 44cef8625..7d48dd79b 100644 --- a/Anima/math-tools/special_functions/animaBesselFunctions.cxx +++ b/Anima/math-tools/special_functions/animaBesselFunctions.cxx @@ -9,160 +9,158 @@ namespace anima { -double log_bessel_i(unsigned int N, double x) -{ - if (x < std::sqrt((double)N+1) / 100) + double log_bessel_i(unsigned int N, double x) { - // tgamma(N) = factorial(N-1) - if (N == 0) - return -std::log(std::tgamma(N+1)); - else - return -std::log(std::tgamma(N+1)) + N * std::log(x / 2.0); + if (x < std::sqrt((double)N + 1) / 100) + { + // tgamma(N) = factorial(N-1) + if (N == 0) + return -std::log(std::tgamma(N + 1)); + else + return -std::log(std::tgamma(N + 1)) + N * std::log(x / 2.0); + } + + if (x <= std::max(9.23 * N + 15.934, 11.26 * N - 236.21)) // before was 600; now garantees an absolute error of maximum 1e-4 between true function and approximation + return std::log(boost::math::cyl_bessel_i(N, x)); + + // // Too big value, switching to approximation + // // Works less and less when orders go up but above that barrier we get non valid values + // double resVal = x - 0.5 * log(2 * M_PI * x); + // + // double secTermSerie = - (4.0 * order * order - 1.0) / (8.0 * x); + // double thirdTermSerie = (4.0 * order * order - 1.0) * (4.0 * order * order - 9.0) / (2.0 * (8.0 * x) * (8.0 * x)); + // + // resVal += log(1.0 + secTermSerie + thirdTermSerie); + + // Correct the problem + // First, compute approx for I_0 + double resVal = x - 0.5 * std::log(2 * M_PI * x); + + double secTermSerie = 1.0 / (8.0 * x); + double thirdTermSerie = 9.0 / (2.0 * (8.0 * x) * (8.0 * x)); + + resVal += std::log(1.0 + secTermSerie + thirdTermSerie); + // Then compute log(I_L) as log(I_0) + sum_{i=1}^L log(bessel_ratio_i(x,L)) + for (unsigned int i = 1; i <= N; ++i) + resVal += std::log(anima::bessel_ratio_i(x, i)); + + return resVal; } - if (x <= std::max(9.23 * N + 15.934, 11.26 * N - 236.21)) // before was 600; now garantees an absolute error of maximum 1e-4 between true function and approximation - return std::log(boost::math::cyl_bessel_i(N,x)); + double bessel_i_lower_bound(unsigned int N, double x) + { + if (x < 1e-2) + return std::pow(x / 2.0, N) / std::tgamma(N + 1); -// // Too big value, switching to approximation -// // Works less and less when orders go up but above that barrier we get non valid values -// double resVal = x - 0.5 * log(2 * M_PI * x); -// -// double secTermSerie = - (4.0 * order * order - 1.0) / (8.0 * x); -// double thirdTermSerie = (4.0 * order * order - 1.0) * (4.0 * order * order - 9.0) / (2.0 * (8.0 * x) * (8.0 * x)); -// -// resVal += log(1.0 + secTermSerie + thirdTermSerie); + double C = x / (N + 0.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); + double D = x / (N + 1.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); - // Correct the problem - // First, compute approx for I_0 - double resVal = x - 0.5 * std::log(2 * M_PI * x); + double res = std::sqrt(2 / x) * std::pow(N + 1, N + 0.5) / std::tgamma(N + 1) * std::exp(x * D) * std::pow(C, N + 0.5); - double secTermSerie = 1.0 / (8.0 * x); - double thirdTermSerie = 9.0 / (2.0 * (8.0 * x) * (8.0 * x)); + return res; + } - resVal += std::log(1.0 + secTermSerie + thirdTermSerie); - // Then compute log(I_L) as log(I_0) + sum_{i=1}^L log(bessel_ratio_i(x,L)) - for (unsigned int i = 1;i <= N;++i) - resVal += std::log(anima::bessel_ratio_i(x, i)); + double log_bessel_i_lower_bound(unsigned int N, double x) + { + if (x < 1e-2) + return N * std::log(x / 2.0) - std::log(std::tgamma(N + 1)); - return resVal; -} + double C = x / (N + 0.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); + double D = x / (N + 1.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); -double bessel_i_lower_bound(unsigned int N, double x) -{ - if (x < 1e-2) - return std::pow(x / 2.0, N) / std::tgamma(N+1); - - double C = x / (N + 0.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); - double D = x / (N + 1.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); - - double res = std::sqrt(2/x) * std::pow(N + 1,N + 0.5) / std::tgamma(N+1) * std::exp(x*D) * std::pow(C, N+0.5); - - return res; -} - -double log_bessel_i_lower_bound(unsigned int N, double x) -{ - if (x < 1e-2) - return N * std::log(x / 2.0) - std::log(std::tgamma(N+1)); - - double C = x / (N + 0.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); - double D = x / (N + 1.5 + std::sqrt(x * x + (N + 1.5) * (N + 1.5))); - - double res = 0.5 * std::log(2/x) + (N + 0.5) * std::log((N + 1) * C) - std::log(std::tgamma(N+1)) + x * D; - - return res; -} - -double bessel_ratio_i(double x, unsigned int N, unsigned int approx_order) -{ - if (N == 0) - return 0; + double res = 0.5 * std::log(2 / x) + (N + 0.5) * std::log((N + 1) * C) - std::log(std::tgamma(N + 1)) + x * D; - // Ajout valeur asymptotique pour x infini - if (x > std::max(70.0, 5.97 * N - 45.25)) // this garantees an absolute error of maximum 1e-4 between true function and approximation - { - double m1 = 4.0 * N * N; - double m0 = 4.0 * (N - 1.0) * (N - 1.0); - return 1.0 - (m1 - m0) / (8.0 * x) + ((m0 - 1.0) * (m0 + 7.0) - 2.0 * (m1 - 1.0) * (m0 - 1.0) + (m1 - 1.0) * (m1 - 9)) / (2.0 * (8.0 * x) * (8.0 * x)); + return res; } - double pk = 1; - double rho = 0; - double resVal = pk; - for (unsigned int i = 1;i <= approx_order;++i) + double bessel_ratio_i(double x, unsigned int N, unsigned int approx_order) { - double ak = anima::ak_support(x,N,i); - rho = - ak * (rho + 1.0) / (1.0 + ak * (1.0 + rho)); - pk = pk * rho; - resVal += pk; + if (N == 0) + return 0; + + // Ajout valeur asymptotique pour x infini + if (x > std::max(70.0, 5.97 * N - 45.25)) // this garantees an absolute error of maximum 1e-4 between true function and approximation + { + double m1 = 4.0 * N * N; + double m0 = 4.0 * (N - 1.0) * (N - 1.0); + return 1.0 - (m1 - m0) / (8.0 * x) + ((m0 - 1.0) * (m0 + 7.0) - 2.0 * (m1 - 1.0) * (m0 - 1.0) + (m1 - 1.0) * (m1 - 9)) / (2.0 * (8.0 * x) * (8.0 * x)); + } + + double pk = 1; + double rho = 0; + double resVal = pk; + for (unsigned int i = 1; i <= approx_order; ++i) + { + double ak = anima::ak_support(x, N, i); + rho = -ak * (rho + 1.0) / (1.0 + ak * (1.0 + rho)); + pk = pk * rho; + resVal += pk; + } + + return anima::a0r_support(x, N) * resVal; } - return anima::a0r_support(x,N) * resVal; -} + double bessel_ratio_i_lower_bound(double x, double nu) + { + return x / (nu - 0.5 + std::sqrt(x * x + (nu + 0.5) * (nu + 0.5))); + } -double bessel_ratio_i_lower_bound(double x, unsigned int N) -{ - double res = x / (N - 0.5 + std::sqrt(x * x + (N + 0.5) * (N + 0.5))); + double bessel_ratio_i_derivative(double x, unsigned int N, unsigned int approx_order) + { + double tmpVal = bessel_ratio_i(x, N, approx_order); + double firstTerm = tmpVal * bessel_ratio_i(x, N + 1, approx_order); + double secondTerm = tmpVal * tmpVal; + double thirdTerm = tmpVal / x; - return res; -} + return firstTerm - secondTerm + thirdTerm; + } -double bessel_ratio_i_derivative(double x, unsigned int N, unsigned int approx_order) -{ - double tmpVal = bessel_ratio_i(x, N, approx_order); - double firstTerm = tmpVal * bessel_ratio_i(x, N + 1, approx_order); - double secondTerm = tmpVal * tmpVal; - double thirdTerm = tmpVal / x; - - return firstTerm - secondTerm + thirdTerm; -} - -double bessel_ratio_i_derivative_approx(double x, unsigned int N) -{ - double tmpVal = bessel_ratio_i_lower_bound(x, N); - double firstTerm = tmpVal * bessel_ratio_i_lower_bound(x, N + 1); - double secondTerm = tmpVal * tmpVal; - double thirdTerm = tmpVal / x; - - return firstTerm - secondTerm + thirdTerm; -} - -double log_bessel_order_derivative_i(double x, unsigned int order, double emc, unsigned int approx_order) -{ - double y = x; - if (y > 2795.0) - y = 2795.0; + double bessel_ratio_i_derivative_approx(double x, unsigned int N) + { + double tmpVal = bessel_ratio_i_lower_bound(x, N); + double firstTerm = tmpVal * bessel_ratio_i_lower_bound(x, N + 1); + double secondTerm = tmpVal * tmpVal; + double thirdTerm = tmpVal / x; - double resVal = log(y / 2.0); + return firstTerm - secondTerm + thirdTerm; + } - double num = 0; - double denom = 0; - for (unsigned int i = 0;i < approx_order;++i) + double log_bessel_order_derivative_i(double x, unsigned int order, unsigned int approx_order) { - double tmpVal = pow(y * y / 4.0, (double)i); - tmpVal /= std::tgamma(i+1); - tmpVal /= std::tgamma(order+i+1); - denom += tmpVal; - tmpVal *= anima::psi_function(order+i+1, emc); - num += tmpVal; + double y = x; + if (y > 2795.0) + y = 2795.0; + + double resVal = log(y / 2.0); + + double num = 0; + double denom = 0; + for (unsigned int i = 0; i < approx_order; ++i) + { + double tmpVal = pow(y * y / 4.0, (double)i); + tmpVal /= std::tgamma(i + 1); + tmpVal /= std::tgamma(order + i + 1); + denom += tmpVal; + tmpVal *= anima::psi_function(order + i + 1); + num += tmpVal; + } + + resVal -= num / denom; + + return resVal; } - resVal -= num / denom; - - return resVal; -} - -double a0r_support(double x, unsigned int N) -{ - return x / (x + 2.0 * N); -} + double a0r_support(double x, unsigned int N) + { + return x / (x + 2.0 * N); + } -double ak_support(double x, unsigned int N, unsigned int k) -{ - if (k == 1) - return - x * (N+0.5) / (2.0 * (N + x * 0.5) * (N + x + 0.5)); - else - return -x * (N + k - 0.5) / (2.0 * (N + x + (k - 1) * 0.5) * (N + x + k * 0.5)); -} + double ak_support(double x, unsigned int N, unsigned int k) + { + if (k == 1) + return -x * (N + 0.5) / (2.0 * (N + x * 0.5) * (N + x + 0.5)); + else + return -x * (N + k - 0.5) / (2.0 * (N + x + (k - 1) * 0.5) * (N + x + k * 0.5)); + } } // end namespace anima diff --git a/Anima/math-tools/special_functions/animaBesselFunctions.h b/Anima/math-tools/special_functions/animaBesselFunctions.h index 93a5d604b..7bf7ebfd3 100644 --- a/Anima/math-tools/special_functions/animaBesselFunctions.h +++ b/Anima/math-tools/special_functions/animaBesselFunctions.h @@ -5,34 +5,34 @@ namespace anima { -//! Computes a lower bound of the modified Bessel function of the first kind: I_{N} (N >= 0) -ANIMASPECIALFUNCTIONS_EXPORT double bessel_i_lower_bound(unsigned int N, double x); + //! Computes a lower bound of the modified Bessel function of the first kind: I_{N} (N >= 0) + ANIMASPECIALFUNCTIONS_EXPORT double bessel_i_lower_bound(unsigned int N, double x); -//! Computes the log of modified Bessel function of the first kind: I_{N} (N >= 0) -ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_i(unsigned int N, double x); + //! Computes the log of modified Bessel function of the first kind: I_{N} (N >= 0) + ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_i(unsigned int N, double x); -//! Computes a lower bound of the log of modified Bessel function of the first kind: I_{N} (N >= 0) -ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_i_lower_bound(unsigned int N, double x); + //! Computes a lower bound of the log of modified Bessel function of the first kind: I_{N} (N >= 0) + ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_i_lower_bound(unsigned int N, double x); -//! Computes the ratio of modified Bessel functions of the first kind: I_{N} / I_{N-1} (N >= 1) -ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i(double x, unsigned int N, unsigned int approx_order = 10); + //! Computes the ratio of modified Bessel functions of the first kind: I_{N} / I_{N-1} + ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i(double x, unsigned int N, unsigned int approx_order = 10); -//! Computes a lower bound of the ratio of modified Bessel functions of the first kind: I_{N} / I_{N-1} (N >= 1) -ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_lower_bound(double x, unsigned int N); - -//! Computes the derivative of the ratio of modified Bessel functions of the first kind: d/dx( I_{N}(x) / I_{N-1}(x) ) (N >= 1) -ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_derivative(double x, unsigned int N, unsigned int approx_order = 10); + //! Computes a lower bound of the ratio of modified Bessel functions of the first kind: I_{\nu} / I_{\nu-1} + ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_lower_bound(double x, double nu); -//! Computes fast and accurate approximation of the derivative of the ratio of modified Bessel functions of the first kind: d/dx( I_{N}(x) / I_{N-1}(x) ) (N >= 1) -ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_derivative_approx(double x, unsigned int N); + //! Computes the derivative of the ratio of modified Bessel functions of the first kind: d/dx( I_{N}(x) / I_{N-1}(x) ) (N >= 1) + ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_derivative(double x, unsigned int N, unsigned int approx_order = 10); -//! Computes the derivative of the log of modified Bessel function of the first kind w.r.t. its order (emc is the Euler-Mascheroni constant) -ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_order_derivative_i(double x, unsigned int order, double emc, unsigned int approx_order = 50); + //! Computes fast and accurate approximation of the derivative of the ratio of modified Bessel functions of the first kind: d/dx( I_{N}(x) / I_{N-1}(x) ) (N >= 1) + ANIMASPECIALFUNCTIONS_EXPORT double bessel_ratio_i_derivative_approx(double x, unsigned int N); -//! Support function for besserl_ratio_i -ANIMASPECIALFUNCTIONS_EXPORT double a0r_support(double x, unsigned int N); + //! Computes the derivative of the log of modified Bessel function of the first kind w.r.t. its order (emc is the Euler-Mascheroni constant) + ANIMASPECIALFUNCTIONS_EXPORT double log_bessel_order_derivative_i(double x, unsigned int order, unsigned int approx_order = 50); -//! Support function for besserl_ratio_i -ANIMASPECIALFUNCTIONS_EXPORT double ak_support(double x, unsigned int N, unsigned int k); + //! Support function for besserl_ratio_i + ANIMASPECIALFUNCTIONS_EXPORT double a0r_support(double x, unsigned int N); + + //! Support function for besserl_ratio_i + ANIMASPECIALFUNCTIONS_EXPORT double ak_support(double x, unsigned int N, unsigned int k); } // end of namespace anima diff --git a/Anima/math-tools/special_functions/animaErrorFunctions.cxx b/Anima/math-tools/special_functions/animaErrorFunctions.cxx index b279a5309..06bfdc7f9 100644 --- a/Anima/math-tools/special_functions/animaErrorFunctions.cxx +++ b/Anima/math-tools/special_functions/animaErrorFunctions.cxx @@ -1,583 +1,587 @@ -#include +#include "animaErrorFunctions.h" -#include -#include #include -#include - -namespace anima -{ +#include -double EvaluateDawsonIntegral(const double x, const bool scaled) -{ - DawsonIntegrand integrand; - integrand.SetXValue(x); - double resVal = boost::math::quadrature::gauss::integrate(integrand, 0.0, 1.0); - return (scaled) ? resVal : x * resVal; -} +#include -//! Numerical recipes implementation of Dawson integral -double EvaluateDawsonFunctionNR(double x) +namespace anima { - unsigned int nmax = 6; - std::vector c(nmax); - double h = 0.4; - double a1 = 2.0/3.0; - double a2 = 0.4; - double a3 = 2.0 / 7.0; - for (unsigned int i = 0;i < nmax;++i) + double EvaluateDawsonIntegral(const double x, const bool scaled) { - double expInVal = (2*i + 1) * h * (2*i + 1) * h; - c[i] = std::exp(- expInVal); + DawsonIntegrand integrand; + integrand.SetXValue(x); + auto f1 = [&integrand](double t) + { return integrand(t); }; + double resVal = boost::math::quadrature::gauss_kronrod::integrate(f1, 0.0, 1.0); + return (scaled) ? resVal : x * resVal; } - if (std::abs(x) < 0.2) - return x * (1.0 - a1*x*x*(1.0 - a2*x*x*(1.0 - a3*x*x))); + //! Numerical recipes implementation of Dawson integral + double EvaluateDawsonFunctionNR(double x) + { + unsigned int nmax = 6; + std::vector c(nmax); + double h = 0.4; + double a1 = 2.0 / 3.0; + double a2 = 0.4; + double a3 = 2.0 / 7.0; - unsigned int n0 = 2 * (int)(0.5*std::abs(x) / h + 0.5); - double xp = std::abs(x) - n0 * h; - double e1 = std::exp(2.0 * xp * h); - double e2 = e1 * e1; - double d1 = n0 + 1; - double d2 = d1 - 2.0; + for (unsigned int i = 0; i < nmax; ++i) + { + double expInVal = (2 * i + 1) * h * (2 * i + 1) * h; + c[i] = std::exp(-expInVal); + } - double sum = 0; - for (unsigned int i = 0;i < nmax;i++,d1+=2.0,d2-=2.0,e1*=e2) - sum += c[i] * (e1 / d1 + 1.0 / (d2 * e1)); + if (std::abs(x) < 0.2) + return x * (1.0 - a1 * x * x * (1.0 - a2 * x * x * (1.0 - a3 * x * x))); - double resVal = (1.0 / std::sqrt(M_PI)) * sum * std::exp(- xp*xp); - if (x < 0) - resVal *= -1; + unsigned int n0 = 2 * (int)(0.5 * std::abs(x) / h + 0.5); + double xp = std::abs(x) - n0 * h; + double e1 = std::exp(2.0 * xp * h); + double e2 = e1 * e1; + double d1 = n0 + 1; + double d2 = d1 - 2.0; - return resVal; -} + double sum = 0; + for (unsigned int i = 0; i < nmax; i++, d1 += 2.0, d2 -= 2.0, e1 *= e2) + sum += c[i] * (e1 / d1 + 1.0 / (d2 * e1)); -//! compute Dawson(x) = sqrt(pi)/2 * exp(-x^2) * erfi(x) -double EvaluateDawsonFunction(double x) -{ - const double spi2 = std::sqrt(M_PI) / 2.0; - return spi2 * EvaluateWImFunction(x); -} + double resVal = (1.0 / std::sqrt(M_PI)) * sum * std::exp(-xp * xp); + if (x < 0) + resVal *= -1; -double EvaluateWImFunction(double x) -{ - const double ispi = 1 / std::sqrt(M_PI); - if (x >= 0) - { - if (x > 45) - { - // continued-fraction expansion is faster - if (x > 5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); - } + return resVal; + } - return EvaluateWImY100Function(100/(1+x), x); + //! compute Dawson(x) = sqrt(pi)/2 * exp(-x^2) * erfi(x) + double EvaluateDawsonFunction(double x) + { + const double spi2 = std::sqrt(M_PI) / 2.0; + return spi2 * EvaluateWImFunction(x); } - else + + double EvaluateWImFunction(double x) { - // = -FADDEEVA(w_im)(-x) - if (x < -45) - { - // continued-fraction expansion is faster - if (x < -5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); + const double ispi = 1 / std::sqrt(M_PI); + if (x >= 0) + { + if (x > 45) + { + // continued-fraction expansion is faster + if (x > 5e7) // 1-term expansion, important to avoid overflow + return ispi / x; + /* 5-term expansion (rely on compiler for CSE), simplified from: + ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ + return ispi * ((x * x) * (x * x - 4.5) + 2) / (x * ((x * x) * (x * x - 5) + 3.75)); + } + + return EvaluateWImY100Function(100 / (1 + x), x); + } + else + { + // = -FADDEEVA(w_im)(-x) + if (x < -45) + { + // continued-fraction expansion is faster + if (x < -5e7) // 1-term expansion, important to avoid overflow + return ispi / x; + /* 5-term expansion (rely on compiler for CSE), simplified from: + ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ + return ispi * ((x * x) * (x * x - 4.5) + 2) / (x * ((x * x) * (x * x - 5) + 3.75)); + } + return -EvaluateWImY100Function(100 / (1 - x), -x); } - return -EvaluateWImY100Function(100/(1-x), -x); } -} -double EvaluateWImY100Function(double y100, double x) -{ - switch ((int) y100) + double EvaluateWImY100Function(double y100, double x) { + switch ((int)y100) + { case 0: { - double t = 2*y100 - 1; + double t = 2 * y100 - 1; return 0.28351593328822191546e-2 + (0.28494783221378400759e-2 + (0.14427470563276734183e-4 + (0.10939723080231588129e-6 + (0.92474307943275042045e-9 + (0.89128907666450075245e-11 + 0.92974121935111111110e-13 * t) * t) * t) * t) * t) * t; } case 1: { - double t = 2*y100 - 3; + double t = 2 * y100 - 3; return 0.85927161243940350562e-2 + (0.29085312941641339862e-2 + (0.15106783707725582090e-4 + (0.11716709978531327367e-6 + (0.10197387816021040024e-8 + (0.10122678863073360769e-10 + 0.10917479678400000000e-12 * t) * t) * t) * t) * t) * t; } case 2: { - double t = 2*y100 - 5; + double t = 2 * y100 - 5; return 0.14471159831187703054e-1 + (0.29703978970263836210e-2 + (0.15835096760173030976e-4 + (0.12574803383199211596e-6 + (0.11278672159518415848e-8 + (0.11547462300333495797e-10 + 0.12894535335111111111e-12 * t) * t) * t) * t) * t) * t; } case 3: { - double t = 2*y100 - 7; + double t = 2 * y100 - 7; return 0.20476320420324610618e-1 + (0.30352843012898665856e-2 + (0.16617609387003727409e-4 + (0.13525429711163116103e-6 + (0.12515095552507169013e-8 + (0.13235687543603382345e-10 + 0.15326595042666666667e-12 * t) * t) * t) * t) * t) * t; } case 4: { - double t = 2*y100 - 9; + double t = 2 * y100 - 9; return 0.26614461952489004566e-1 + (0.31034189276234947088e-2 + (0.17460268109986214274e-4 + (0.14582130824485709573e-6 + (0.13935959083809746345e-8 + (0.15249438072998932900e-10 + 0.18344741882133333333e-12 * t) * t) * t) * t) * t) * t; } case 5: { - double t = 2*y100 - 11; + double t = 2 * y100 - 11; return 0.32892330248093586215e-1 + (0.31750557067975068584e-2 + (0.18369907582308672632e-4 + (0.15761063702089457882e-6 + (0.15577638230480894382e-8 + (0.17663868462699097951e-10 + (0.22126732680711111111e-12 + 0.30273474177737853668e-14 * t) * t) * t) * t) * t) * t) * t; } case 6: { - double t = 2*y100 - 13; + double t = 2 * y100 - 13; return 0.39317207681134336024e-1 + (0.32504779701937539333e-2 + (0.19354426046513400534e-4 + (0.17081646971321290539e-6 + (0.17485733959327106250e-8 + (0.20593687304921961410e-10 + (0.26917401949155555556e-12 + 0.38562123837725712270e-14 * t) * t) * t) * t) * t) * t) * t; } case 7: { - double t = 2*y100 - 15; + double t = 2 * y100 - 15; return 0.45896976511367738235e-1 + (0.33300031273110976165e-2 + (0.20423005398039037313e-4 + (0.18567412470376467303e-6 + (0.19718038363586588213e-8 + (0.24175006536781219807e-10 + (0.33059982791466666666e-12 + 0.49756574284439426165e-14 * t) * t) * t) * t) * t) * t) * t; } case 8: { - double t = 2*y100 - 17; + double t = 2 * y100 - 17; return 0.52640192524848962855e-1 + (0.34139883358846720806e-2 + (0.21586390240603337337e-4 + (0.20247136501568904646e-6 + (0.22348696948197102935e-8 + (0.28597516301950162548e-10 + (0.41045502119111111110e-12 + 0.65151614515238361946e-14 * t) * t) * t) * t) * t) * t) * t; } case 9: { - double t = 2*y100 - 19; + double t = 2 * y100 - 19; return 0.59556171228656770456e-1 + (0.35028374386648914444e-2 + (0.22857246150998562824e-4 + (0.22156372146525190679e-6 + (0.25474171590893813583e-8 + (0.34122390890697400584e-10 + (0.51593189879111111110e-12 + 0.86775076853908006938e-14 * t) * t) * t) * t) * t) * t) * t; } case 10: { - double t = 2*y100 - 21; + double t = 2 * y100 - 21; return 0.66655089485108212551e-1 + (0.35970095381271285568e-2 + (0.24250626164318672928e-4 + (0.24339561521785040536e-6 + (0.29221990406518411415e-8 + (0.41117013527967776467e-10 + (0.65786450716444444445e-12 + 0.11791885745450623331e-13 * t) * t) * t) * t) * t) * t) * t; } case 11: { - double t = 2*y100 - 23; + double t = 2 * y100 - 23; return 0.73948106345519174661e-1 + (0.36970297216569341748e-2 + (0.25784588137312868792e-4 + (0.26853012002366752770e-6 + (0.33763958861206729592e-8 + (0.50111549981376976397e-10 + (0.85313857496888888890e-12 + 0.16417079927706899860e-13 * t) * t) * t) * t) * t) * t) * t; } case 12: { - double t = 2*y100 - 25; + double t = 2 * y100 - 25; return 0.81447508065002963203e-1 + (0.38035026606492705117e-2 + (0.27481027572231851896e-4 + (0.29769200731832331364e-6 + (0.39336816287457655076e-8 + (0.61895471132038157624e-10 + (0.11292303213511111111e-11 + 0.23558532213703884304e-13 * t) * t) * t) * t) * t) * t) * t; } case 13: { - double t = 2*y100 - 27; + double t = 2 * y100 - 27; return 0.89166884027582716628e-1 + (0.39171301322438946014e-2 + (0.29366827260422311668e-4 + (0.33183204390350724895e-6 + (0.46276006281647330524e-8 + (0.77692631378169813324e-10 + (0.15335153258844444444e-11 + 0.35183103415916026911e-13 * t) * t) * t) * t) * t) * t) * t; } case 14: { - double t = 2*y100 - 29; + double t = 2 * y100 - 29; return 0.97121342888032322019e-1 + (0.40387340353207909514e-2 + (0.31475490395950776930e-4 + (0.37222714227125135042e-6 + (0.55074373178613809996e-8 + (0.99509175283990337944e-10 + (0.21552645758222222222e-11 + 0.55728651431872687605e-13 * t) * t) * t) * t) * t) * t) * t; } case 15: { - double t = 2*y100 - 31; + double t = 2 * y100 - 31; return 0.10532778218603311137e0 + (0.41692873614065380607e-2 + (0.33849549774889456984e-4 + (0.42064596193692630143e-6 + (0.66494579697622432987e-8 + (0.13094103581931802337e-9 + (0.31896187409777777778e-11 + 0.97271974184476560742e-13 * t) * t) * t) * t) * t) * t) * t; } case 16: { - double t = 2*y100 - 33; + double t = 2 * y100 - 33; return 0.11380523107427108222e0 + (0.43099572287871821013e-2 + (0.36544324341565929930e-4 + (0.47965044028581857764e-6 + (0.81819034238463698796e-8 + (0.17934133239549647357e-9 + (0.50956666166186293627e-11 + (0.18850487318190638010e-12 + 0.79697813173519853340e-14 * t) * t) * t) * t) * t) * t) * t) * t; } case 17: { - double t = 2*y100 - 35; + double t = 2 * y100 - 35; return 0.12257529703447467345e0 + (0.44621675710026986366e-2 + (0.39634304721292440285e-4 + (0.55321553769873381819e-6 + (0.10343619428848520870e-7 + (0.26033830170470368088e-9 + (0.87743837749108025357e-11 + (0.34427092430230063401e-12 + 0.10205506615709843189e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 18: { - double t = 2*y100 - 37; + double t = 2 * y100 - 37; return 0.13166276955656699478e0 + (0.46276970481783001803e-2 + (0.43225026380496399310e-4 + (0.64799164020016902656e-6 + (0.13580082794704641782e-7 + (0.39839800853954313927e-9 + (0.14431142411840000000e-10 + 0.42193457308830027541e-12 * t) * t) * t) * t) * t) * t) * t; } case 19: { - double t = 2*y100 - 39; + double t = 2 * y100 - 39; return 0.14109647869803356475e0 + (0.48088424418545347758e-2 + (0.47474504753352150205e-4 + (0.77509866468724360352e-6 + (0.18536851570794291724e-7 + (0.60146623257887570439e-9 + (0.18533978397305276318e-10 + (0.41033845938901048380e-13 - 0.46160680279304825485e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 20: { - double t = 2*y100 - 41; + double t = 2 * y100 - 41; return 0.15091057940548936603e0 + (0.50086864672004685703e-2 + (0.52622482832192230762e-4 + (0.95034664722040355212e-6 + (0.25614261331144718769e-7 + (0.80183196716888606252e-9 + (0.12282524750534352272e-10 + (-0.10531774117332273617e-11 - 0.86157181395039646412e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 21: { - double t = 2*y100 - 43; + double t = 2 * y100 - 43; return 0.16114648116017010770e0 + (0.52314661581655369795e-2 + (0.59005534545908331315e-4 + (0.11885518333915387760e-5 + (0.33975801443239949256e-7 + (0.82111547144080388610e-9 + (-0.12357674017312854138e-10 + (-0.24355112256914479176e-11 - 0.75155506863572930844e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 22: { - double t = 2*y100 - 45; + double t = 2 * y100 - 45; return 0.17185551279680451144e0 + (0.54829002967599420860e-2 + (0.67013226658738082118e-4 + (0.14897400671425088807e-5 + (0.40690283917126153701e-7 + (0.44060872913473778318e-9 + (-0.52641873433280000000e-10 - 0.30940587864543343124e-11 * t) * t) * t) * t) * t) * t) * t; } - case 23: { - double t = 2*y100 - 47; + case 23: + { + double t = 2 * y100 - 47; return 0.18310194559815257381e0 + (0.57701559375966953174e-2 + (0.76948789401735193483e-4 + (0.18227569842290822512e-5 + (0.41092208344387212276e-7 + (-0.44009499965694442143e-9 + (-0.92195414685628803451e-10 + (-0.22657389705721753299e-11 + 0.10004784908106839254e-12 * t) * t) * t) * t) * t) * t) * t) * t; } case 24: { - double t = 2*y100 - 49; + double t = 2 * y100 - 49; return 0.19496527191546630345e0 + (0.61010853144364724856e-2 + (0.88812881056342004864e-4 + (0.21180686746360261031e-5 + (0.30652145555130049203e-7 + (-0.16841328574105890409e-8 + (-0.11008129460612823934e-9 + (-0.12180794204544515779e-12 + 0.15703325634590334097e-12 * t) * t) * t) * t) * t) * t) * t) * t; } case 25: { - double t = 2*y100 - 51; + double t = 2 * y100 - 51; return 0.20754006813966575720e0 + (0.64825787724922073908e-2 + (0.10209599627522311893e-3 + (0.22785233392557600468e-5 + (0.73495224449907568402e-8 + (-0.29442705974150112783e-8 + (-0.94082603434315016546e-10 + (0.23609990400179321267e-11 + 0.14141908654269023788e-12 * t) * t) * t) * t) * t) * t) * t) * t; } case 26: { - double t = 2*y100 - 53; + double t = 2 * y100 - 53; return 0.22093185554845172146e0 + (0.69182878150187964499e-2 + (0.11568723331156335712e-3 + (0.22060577946323627739e-5 + (-0.26929730679360840096e-7 + (-0.38176506152362058013e-8 + (-0.47399503861054459243e-10 + (0.40953700187172127264e-11 + 0.69157730376118511127e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 27: { - double t = 2*y100 - 55; + double t = 2 * y100 - 55; return 0.23524827304057813918e0 + (0.74063350762008734520e-2 + (0.12796333874615790348e-3 + (0.18327267316171054273e-5 + (-0.66742910737957100098e-7 + (-0.40204740975496797870e-8 + (0.14515984139495745330e-10 + (0.44921608954536047975e-11 - 0.18583341338983776219e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 28: { - double t = 2*y100 - 57; + double t = 2 * y100 - 57; return 0.25058626331812744775e0 + (0.79377285151602061328e-2 + (0.13704268650417478346e-3 + (0.11427511739544695861e-5 + (-0.10485442447768377485e-6 + (-0.34850364756499369763e-8 + (0.72656453829502179208e-10 + (0.36195460197779299406e-11 - 0.84882136022200714710e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 29: { - double t = 2*y100 - 59; + double t = 2 * y100 - 59; return 0.26701724900280689785e0 + (0.84959936119625864274e-2 + (0.14112359443938883232e-3 + (0.17800427288596909634e-6 + (-0.13443492107643109071e-6 + (-0.23512456315677680293e-8 + (0.11245846264695936769e-9 + (0.19850501334649565404e-11 - 0.11284666134635050832e-12 * t) * t) * t) * t) * t) * t) * t) * t; } case 30: { - double t = 2*y100 - 61; + double t = 2 * y100 - 61; return 0.28457293586253654144e0 + (0.90581563892650431899e-2 + (0.13880520331140646738e-3 + (-0.97262302362522896157e-6 + (-0.15077100040254187366e-6 + (-0.88574317464577116689e-9 + (0.12760311125637474581e-9 + (0.20155151018282695055e-12 - 0.10514169375181734921e-12 * t) * t) * t) * t) * t) * t) * t) * t; } case 31: { - double t = 2*y100 - 63; + double t = 2 * y100 - 63; return 0.30323425595617385705e0 + (0.95968346790597422934e-2 + (0.12931067776725883939e-3 + (-0.21938741702795543986e-5 + (-0.15202888584907373963e-6 + (0.61788350541116331411e-9 + (0.11957835742791248256e-9 + (-0.12598179834007710908e-11 - 0.75151817129574614194e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 32: { - double t = 2*y100 - 65; + double t = 2 * y100 - 65; return 0.32292521181517384379e0 + (0.10082957727001199408e-1 + (0.11257589426154962226e-3 + (-0.33670890319327881129e-5 + (-0.13910529040004008158e-6 + (0.19170714373047512945e-8 + (0.94840222377720494290e-10 + (-0.21650018351795353201e-11 - 0.37875211678024922689e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 33: { - double t = 2*y100 - 67; + double t = 2 * y100 - 67; return 0.34351233557911753862e0 + (0.10488575435572745309e-1 + (0.89209444197248726614e-4 + (-0.43893459576483345364e-5 + (-0.11488595830450424419e-6 + (0.28599494117122464806e-8 + (0.61537542799857777779e-10 - 0.24935749227658002212e-11 * t) * t) * t) * t) * t) * t) * t; } case 34: { - double t = 2*y100 - 69; + double t = 2 * y100 - 69; return 0.36480946642143669093e0 + (0.10789304203431861366e-1 + (0.60357993745283076834e-4 + (-0.51855862174130669389e-5 + (-0.83291664087289801313e-7 + (0.33898011178582671546e-8 + (0.27082948188277716482e-10 + (-0.23603379397408694974e-11 + 0.19328087692252869842e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 35: { - double t = 2*y100 - 71; + double t = 2 * y100 - 71; return 0.38658679935694939199e0 + (0.10966119158288804999e-1 + (0.27521612041849561426e-4 + (-0.57132774537670953638e-5 + (-0.48404772799207914899e-7 + (0.35268354132474570493e-8 + (-0.32383477652514618094e-11 + (-0.19334202915190442501e-11 + 0.32333189861286460270e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 36: { - double t = 2*y100 - 73; + double t = 2 * y100 - 73; return 0.40858275583808707870e0 + (0.11006378016848466550e-1 + (-0.76396376685213286033e-5 + (-0.59609835484245791439e-5 + (-0.13834610033859313213e-7 + (0.33406952974861448790e-8 + (-0.26474915974296612559e-10 + (-0.13750229270354351983e-11 + 0.36169366979417390637e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 37: { - double t = 2*y100 - 75; + double t = 2 * y100 - 75; return 0.43051714914006682977e0 + (0.10904106549500816155e-1 + (-0.43477527256787216909e-4 + (-0.59429739547798343948e-5 + (0.17639200194091885949e-7 + (0.29235991689639918688e-8 + (-0.41718791216277812879e-10 + (-0.81023337739508049606e-12 + 0.33618915934461994428e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 38: { - double t = 2*y100 - 77; + double t = 2 * y100 - 77; return 0.45210428135559607406e0 + (0.10659670756384400554e-1 + (-0.78488639913256978087e-4 + (-0.56919860886214735936e-5 + (0.44181850467477733407e-7 + (0.23694306174312688151e-8 + (-0.49492621596685443247e-10 + (-0.31827275712126287222e-12 + 0.27494438742721623654e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 39: { - double t = 2*y100 - 79; + double t = 2 * y100 - 79; return 0.47306491195005224077e0 + (0.10279006119745977570e-1 + (-0.11140268171830478306e-3 + (-0.52518035247451432069e-5 + (0.64846898158889479518e-7 + (0.17603624837787337662e-8 + (-0.51129481592926104316e-10 + (0.62674584974141049511e-13 + 0.20055478560829935356e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 40: { - double t = 2*y100 - 81; + double t = 2 * y100 - 81; return 0.49313638965719857647e0 + (0.97725799114772017662e-2 + (-0.14122854267291533334e-3 + (-0.46707252568834951907e-5 + (0.79421347979319449524e-7 + (0.11603027184324708643e-8 + (-0.48269605844397175946e-10 + (0.32477251431748571219e-12 + 0.12831052634143527985e-13 * t) * t) * t) * t) * t) * t) * t) * t; } case 41: { - double t = 2*y100 - 83; + double t = 2 * y100 - 83; return 0.51208057433416004042e0 + (0.91542422354009224951e-2 + (-0.16726530230228647275e-3 + (-0.39964621752527649409e-5 + (0.88232252903213171454e-7 + (0.61343113364949928501e-9 + (-0.42516755603130443051e-10 + (0.47910437172240209262e-12 + 0.66784341874437478953e-14 * t) * t) * t) * t) * t) * t) * t) * t; } case 42: { - double t = 2*y100 - 85; + double t = 2 * y100 - 85; return 0.52968945458607484524e0 + (0.84400880445116786088e-2 + (-0.18908729783854258774e-3 + (-0.32725905467782951931e-5 + (0.91956190588652090659e-7 + (0.14593989152420122909e-9 + (-0.35239490687644444445e-10 + 0.54613829888448694898e-12 * t) * t) * t) * t) * t) * t) * t; } case 43: { - double t = 2*y100 - 87; + double t = 2 * y100 - 87; return 0.54578857454330070965e0 + (0.76474155195880295311e-2 + (-0.20651230590808213884e-3 + (-0.25364339140543131706e-5 + (0.91455367999510681979e-7 + (-0.23061359005297528898e-9 + (-0.27512928625244444444e-10 + 0.54895806008493285579e-12 * t) * t) * t) * t) * t) * t) * t; } case 44: { - double t = 2*y100 - 89; + double t = 2 * y100 - 89; return 0.56023851910298493910e0 + (0.67938321739997196804e-2 + (-0.21956066613331411760e-3 + (-0.18181127670443266395e-5 + (0.87650335075416845987e-7 + (-0.51548062050366615977e-9 + (-0.20068462174044444444e-10 + 0.50912654909758187264e-12 * t) * t) * t) * t) * t) * t) * t; } case 45: { - double t = 2*y100 - 91; + double t = 2 * y100 - 91; return 0.57293478057455721150e0 + (0.58965321010394044087e-2 + (-0.22841145229276575597e-3 + (-0.11404605562013443659e-5 + (0.81430290992322326296e-7 + (-0.71512447242755357629e-9 + (-0.13372664928000000000e-10 + 0.44461498336689298148e-12 * t) * t) * t) * t) * t) * t) * t; } case 46: { - double t = 2*y100 - 93; + double t = 2 * y100 - 93; return 0.58380635448407827360e0 + (0.49717469530842831182e-2 + (-0.23336001540009645365e-3 + (-0.51952064448608850822e-6 + (0.73596577815411080511e-7 + (-0.84020916763091566035e-9 + (-0.76700972702222222221e-11 + 0.36914462807972467044e-12 * t) * t) * t) * t) * t) * t) * t; } case 47: { - double t = 2*y100 - 95; + double t = 2 * y100 - 95; return 0.59281340237769489597e0 + (0.40343592069379730568e-2 + (-0.23477963738658326185e-3 + (0.34615944987790224234e-7 + (0.64832803248395814574e-7 + (-0.90329163587627007971e-9 + (-0.30421940400000000000e-11 + 0.29237386653743536669e-12 * t) * t) * t) * t) * t) * t) * t; } case 48: { - double t = 2*y100 - 97; + double t = 2 * y100 - 97; return 0.59994428743114271918e0 + (0.30976579788271744329e-2 + (-0.23308875765700082835e-3 + (0.51681681023846925160e-6 + (0.55694594264948268169e-7 + (-0.91719117313243464652e-9 + (0.53982743680000000000e-12 + 0.22050829296187771142e-12 * t) * t) * t) * t) * t) * t) * t; } case 49: { - double t = 2*y100 - 99; + double t = 2 * y100 - 99; return 0.60521224471819875444e0 + (0.21732138012345456060e-2 + (-0.22872428969625997456e-3 + (0.92588959922653404233e-6 + (0.46612665806531930684e-7 + (-0.89393722514414153351e-9 + (0.31718550353777777778e-11 + 0.15705458816080549117e-12 * t) * t) * t) * t) * t) * t) * t; } case 50: { - double t = 2*y100 - 101; + double t = 2 * y100 - 101; return 0.60865189969791123620e0 + (0.12708480848877451719e-2 + (-0.22212090111534847166e-3 + (0.12636236031532793467e-5 + (0.37904037100232937574e-7 + (-0.84417089968101223519e-9 + (0.49843180828444444445e-11 + 0.10355439441049048273e-12 * t) * t) * t) * t) * t) * t) * t; } case 51: { - double t = 2*y100 - 103; + double t = 2 * y100 - 103; return 0.61031580103499200191e0 + (0.39867436055861038223e-3 + (-0.21369573439579869291e-3 + (0.15339402129026183670e-5 + (0.29787479206646594442e-7 + (-0.77687792914228632974e-9 + (0.61192452741333333334e-11 + 0.60216691829459295780e-13 * t) * t) * t) * t) * t) * t) * t; } case 52: { - double t = 2*y100 - 105; + double t = 2 * y100 - 105; return 0.61027109047879835868e0 + (-0.43680904508059878254e-3 + (-0.20383783788303894442e-3 + (0.17421743090883439959e-5 + (0.22400425572175715576e-7 + (-0.69934719320045128997e-9 + (0.67152759655111111110e-11 + 0.26419960042578359995e-13 * t) * t) * t) * t) * t) * t) * t; } case 53: { - double t = 2*y100 - 107; + double t = 2 * y100 - 107; return 0.60859639489217430521e0 + (-0.12305921390962936873e-2 + (-0.19290150253894682629e-3 + (0.18944904654478310128e-5 + (0.15815530398618149110e-7 + (-0.61726850580964876070e-9 + 0.68987888999111111110e-11 * t) * t) * t) * t) * t) * t; } case 54: { - double t = 2*y100 - 109; + double t = 2 * y100 - 109; return 0.60537899426486075181e0 + (-0.19790062241395705751e-2 + (-0.18120271393047062253e-3 + (0.19974264162313241405e-5 + (0.10055795094298172492e-7 + (-0.53491997919318263593e-9 + (0.67794550295111111110e-11 - 0.17059208095741511603e-13 * t) * t) * t) * t) * t) * t) * t; } case 55: { - double t = 2*y100 - 111; + double t = 2 * y100 - 111; return 0.60071229457904110537e0 + (-0.26795676776166354354e-2 + (-0.16901799553627508781e-3 + (0.20575498324332621581e-5 + (0.51077165074461745053e-8 + (-0.45536079828057221858e-9 + (0.64488005516444444445e-11 - 0.29311677573152766338e-13 * t) * t) * t) * t) * t) * t) * t; } case 56: { - double t = 2*y100 - 113; + double t = 2 * y100 - 113; return 0.59469361520112714738e0 + (-0.33308208190600993470e-2 + (-0.15658501295912405679e-3 + (0.20812116912895417272e-5 + (0.93227468760614182021e-9 + (-0.38066673740116080415e-9 + (0.59806790359111111110e-11 - 0.36887077278950440597e-13 * t) * t) * t) * t) * t) * t) * t; } case 57: { - double t = 2*y100 - 115; + double t = 2 * y100 - 115; return 0.58742228631775388268e0 + (-0.39321858196059227251e-2 + (-0.14410441141450122535e-3 + (0.20743790018404020716e-5 + (-0.25261903811221913762e-8 + (-0.31212416519526924318e-9 + (0.54328422462222222221e-11 - 0.40864152484979815972e-13 * t) * t) * t) * t) * t) * t) * t; } case 58: { - double t = 2*y100 - 117; + double t = 2 * y100 - 117; return 0.57899804200033018447e0 + (-0.44838157005618913447e-2 + (-0.13174245966501437965e-3 + (0.20425306888294362674e-5 + (-0.53330296023875447782e-8 + (-0.25041289435539821014e-9 + (0.48490437205333333334e-11 - 0.42162206939169045177e-13 * t) * t) * t) * t) * t) * t) * t; } case 59: { - double t = 2*y100 - 119; + double t = 2 * y100 - 119; return 0.56951968796931245974e0 + (-0.49864649488074868952e-2 + (-0.11963416583477567125e-3 + (0.19906021780991036425e-5 + (-0.75580140299436494248e-8 + (-0.19576060961919820491e-9 + (0.42613011928888888890e-11 - 0.41539443304115604377e-13 * t) * t) * t) * t) * t) * t) * t; } case 60: { - double t = 2*y100 - 121; + double t = 2 * y100 - 121; return 0.55908401930063918964e0 + (-0.54413711036826877753e-2 + (-0.10788661102511914628e-3 + (0.19229663322982839331e-5 + (-0.92714731195118129616e-8 + (-0.14807038677197394186e-9 + (0.36920870298666666666e-11 - 0.39603726688419162617e-13 * t) * t) * t) * t) * t) * t) * t; } case 61: { - double t = 2*y100 - 123; + double t = 2 * y100 - 123; return 0.54778496152925675315e0 + (-0.58501497933213396670e-2 + (-0.96582314317855227421e-4 + (0.18434405235069270228e-5 + (-0.10541580254317078711e-7 + (-0.10702303407788943498e-9 + (0.31563175582222222222e-11 - 0.36829748079110481422e-13 * t) * t) * t) * t) * t) * t) * t; } case 62: { - double t = 2*y100 - 125; + double t = 2 * y100 - 125; return 0.53571290831682823999e0 + (-0.62147030670760791791e-2 + (-0.85782497917111760790e-4 + (0.17553116363443470478e-5 + (-0.11432547349815541084e-7 + (-0.72157091369041330520e-10 + (0.26630811607111111111e-11 - 0.33578660425893164084e-13 * t) * t) * t) * t) * t) * t) * t; } case 63: { - double t = 2*y100 - 127; + double t = 2 * y100 - 127; return 0.52295422962048434978e0 + (-0.65371404367776320720e-2 + (-0.75530164941473343780e-4 + (0.16613725797181276790e-5 + (-0.12003521296598910761e-7 + (-0.42929753689181106171e-10 + (0.22170894940444444444e-11 - 0.30117697501065110505e-13 * t) * t) * t) * t) * t) * t) * t; } case 64: { - double t = 2*y100 - 129; + double t = 2 * y100 - 129; return 0.50959092577577886140e0 + (-0.68197117603118591766e-2 + (-0.65852936198953623307e-4 + (0.15639654113906716939e-5 + (-0.12308007991056524902e-7 + (-0.18761997536910939570e-10 + (0.18198628922666666667e-11 - 0.26638355362285200932e-13 * t) * t) * t) * t) * t) * t) * t; } case 65: { - double t = 2*y100 - 131; + double t = 2 * y100 - 131; return 0.49570040481823167970e0 + (-0.70647509397614398066e-2 + (-0.56765617728962588218e-4 + (0.14650274449141448497e-5 + (-0.12393681471984051132e-7 + (0.92904351801168955424e-12 + (0.14706755960177777778e-11 - 0.23272455351266325318e-13 * t) * t) * t) * t) * t) * t) * t; } case 66: { - double t = 2*y100 - 133; + double t = 2 * y100 - 133; return 0.48135536250935238066e0 + (-0.72746293327402359783e-2 + (-0.48272489495730030780e-4 + (0.13661377309113939689e-5 + (-0.12302464447599382189e-7 + (0.16707760028737074907e-10 + (0.11672928324444444444e-11 - 0.20105801424709924499e-13 * t) * t) * t) * t) * t) * t) * t; } case 67: { - double t = 2*y100 - 135; + double t = 2 * y100 - 135; return 0.46662374675511439448e0 + (-0.74517177649528487002e-2 + (-0.40369318744279128718e-4 + (0.12685621118898535407e-5 + (-0.12070791463315156250e-7 + (0.29105507892605823871e-10 + (0.90653314645333333334e-12 - 0.17189503312102982646e-13 * t) * t) * t) * t) * t) * t) * t; } case 68: { - double t = 2*y100 - 137; + double t = 2 * y100 - 137; return 0.45156879030168268778e0 + (-0.75983560650033817497e-2 + (-0.33045110380705139759e-4 + (0.11732956732035040896e-5 + (-0.11729986947158201869e-7 + (0.38611905704166441308e-10 + (0.68468768305777777779e-12 - 0.14549134330396754575e-13 * t) * t) * t) * t) * t) * t) * t; } - case 69: { - double t = 2*y100 - 139; + case 69: + { + double t = 2 * y100 - 139; return 0.43624909769330896904e0 + (-0.77168291040309554679e-2 + (-0.26283612321339907756e-4 + (0.10811018836893550820e-5 + (-0.11306707563739851552e-7 + (0.45670446788529607380e-10 + (0.49782492549333333334e-12 - 0.12191983967561779442e-13 * t) * t) * t) * t) * t) * t) * t; } case 70: { - double t = 2*y100 - 141; + double t = 2 * y100 - 141; return 0.42071877443548481181e0 + (-0.78093484015052730097e-2 + (-0.20064596897224934705e-4 + (0.99254806680671890766e-6 + (-0.10823412088884741451e-7 + (0.50677203326904716247e-10 + (0.34200547594666666666e-12 - 0.10112698698356194618e-13 * t) * t) * t) * t) * t) * t) * t; } case 71: { - double t = 2*y100 - 143; + double t = 2 * y100 - 143; return 0.40502758809710844280e0 + (-0.78780384460872937555e-2 + (-0.14364940764532853112e-4 + (0.90803709228265217384e-6 + (-0.10298832847014466907e-7 + (0.53981671221969478551e-10 + (0.21342751381333333333e-12 - 0.82975901848387729274e-14 * t) * t) * t) * t) * t) * t) * t; } case 72: { - double t = 2*y100 - 145; + double t = 2 * y100 - 145; return 0.38922115269731446690e0 + (-0.79249269708242064120e-2 + (-0.91595258799106970453e-5 + (0.82783535102217576495e-6 + (-0.97484311059617744437e-8 + (0.55889029041660225629e-10 + (0.10851981336888888889e-12 - 0.67278553237853459757e-14 * t) * t) * t) * t) * t) * t) * t; } case 73: { - double t = 2*y100 - 147; + double t = 2 * y100 - 147; return 0.37334112915460307335e0 + (-0.79519385109223148791e-2 + (-0.44219833548840469752e-5 + (0.75209719038240314732e-6 + (-0.91848251458553190451e-8 + (0.56663266668051433844e-10 + (0.23995894257777777778e-13 - 0.53819475285389344313e-14 * t) * t) * t) * t) * t) * t) * t; } case 74: { - double t = 2*y100 - 149; + double t = 2 * y100 - 149; return 0.35742543583374223085e0 + (-0.79608906571527956177e-2 + (-0.12530071050975781198e-6 + (0.68088605744900552505e-6 + (-0.86181844090844164075e-8 + (0.56530784203816176153e-10 + (-0.43120012248888888890e-13 - 0.42372603392496813810e-14 * t) * t) * t) * t) * t) * t) * t; } case 75: { - double t = 2*y100 - 151; + double t = 2 * y100 - 151; return 0.34150846431979618536e0 + (-0.79534924968773806029e-2 + (0.37576885610891515813e-5 + (0.61419263633090524326e-6 + (-0.80565865409945960125e-8 + (0.55684175248749269411e-10 + (-0.95486860764444444445e-13 - 0.32712946432984510595e-14 * t) * t) * t) * t) * t) * t) * t; } case 76: { - double t = 2*y100 - 153; + double t = 2 * y100 - 153; return 0.32562129649136346824e0 + (-0.79313448067948884309e-2 + (0.72539159933545300034e-5 + (0.55195028297415503083e-6 + (-0.75063365335570475258e-8 + (0.54281686749699595941e-10 - 0.13545424295111111111e-12 * t) * t) * t) * t) * t) * t; } case 77: { - double t = 2*y100 - 155; + double t = 2 * y100 - 155; return 0.30979191977078391864e0 + (-0.78959416264207333695e-2 + (0.10389774377677210794e-4 + (0.49404804463196316464e-6 + (-0.69722488229411164685e-8 + (0.52469254655951393842e-10 - 0.16507860650666666667e-12 * t) * t) * t) * t) * t) * t; } case 78: { - double t = 2*y100 - 157; + double t = 2 * y100 - 157; return 0.29404543811214459904e0 + (-0.78486728990364155356e-2 + (0.13190885683106990459e-4 + (0.44034158861387909694e-6 + (-0.64578942561562616481e-8 + (0.50354306498006928984e-10 - 0.18614473550222222222e-12 * t) * t) * t) * t) * t) * t; } case 79: { - double t = 2*y100 - 159; + double t = 2 * y100 - 159; return 0.27840427686253660515e0 + (-0.77908279176252742013e-2 + (0.15681928798708548349e-4 + (0.39066226205099807573e-6 + (-0.59658144820660420814e-8 + (0.48030086420373141763e-10 - 0.20018995173333333333e-12 * t) * t) * t) * t) * t) * t; } case 80: { - double t = 2*y100 - 161; + double t = 2 * y100 - 161; return 0.26288838011163800908e0 + (-0.77235993576119469018e-2 + (0.17886516796198660969e-4 + (0.34482457073472497720e-6 + (-0.54977066551955420066e-8 + (0.45572749379147269213e-10 - 0.20852924954666666667e-12 * t) * t) * t) * t) * t) * t; } case 81: { - double t = 2*y100 - 163; + double t = 2 * y100 - 163; return 0.24751539954181029717e0 + (-0.76480877165290370975e-2 + (0.19827114835033977049e-4 + (0.30263228619976332110e-6 + (-0.50545814570120129947e-8 + (0.43043879374212005966e-10 - 0.21228012028444444444e-12 * t) * t) * t) * t) * t) * t; } case 82: { - double t = 2*y100 - 165; + double t = 2 * y100 - 165; return 0.23230087411688914593e0 + (-0.75653060136384041587e-2 + (0.21524991113020016415e-4 + (0.26388338542539382413e-6 + (-0.46368974069671446622e-8 + (0.40492715758206515307e-10 - 0.21238627815111111111e-12 * t) * t) * t) * t) * t) * t; } case 83: { - double t = 2*y100 - 167; + double t = 2 * y100 - 167; return 0.21725840021297341931e0 + (-0.74761846305979730439e-2 + (0.23000194404129495243e-4 + (0.22837400135642906796e-6 + (-0.42446743058417541277e-8 + (0.37958104071765923728e-10 - 0.20963978568888888889e-12 * t) * t) * t) * t) * t) * t; } case 84: { - double t = 2*y100 - 169; + double t = 2 * y100 - 169; return 0.20239979200788191491e0 + (-0.73815761980493466516e-2 + (0.24271552727631854013e-4 + (0.19590154043390012843e-6 + (-0.38775884642456551753e-8 + (0.35470192372162901168e-10 - 0.20470131678222222222e-12 * t) * t) * t) * t) * t) * t; } case 85: { - double t = 2*y100 - 171; + double t = 2 * y100 - 171; return 0.18773523211558098962e0 + (-0.72822604530339834448e-2 + (0.25356688567841293697e-4 + (0.16626710297744290016e-6 + (-0.35350521468015310830e-8 + (0.33051896213898864306e-10 - 0.19811844544000000000e-12 * t) * t) * t) * t) * t) * t; } case 86: { - double t = 2*y100 - 173; + double t = 2 * y100 - 173; return 0.17327341258479649442e0 + (-0.71789490089142761950e-2 + (0.26272046822383820476e-4 + (0.13927732375657362345e-6 + (-0.32162794266956859603e-8 + (0.30720156036105652035e-10 - 0.19034196304000000000e-12 * t) * t) * t) * t) * t) * t; } case 87: { - double t = 2*y100 - 175; + double t = 2 * y100 - 175; return 0.15902166648328672043e0 + (-0.70722899934245504034e-2 + (0.27032932310132226025e-4 + (0.11474573347816568279e-6 + (-0.29203404091754665063e-8 + (0.28487010262547971859e-10 - 0.18174029063111111111e-12 * t) * t) * t) * t) * t) * t; } case 88: { - double t = 2*y100 - 177; + double t = 2 * y100 - 177; return 0.14498609036610283865e0 + (-0.69628725220045029273e-2 + (0.27653554229160596221e-4 + (0.92493727167393036470e-7 + (-0.26462055548683583849e-8 + (0.26360506250989943739e-10 - 0.17261211260444444444e-12 * t) * t) * t) * t) * t) * t; } case 89: { - double t = 2*y100 - 179; + double t = 2 * y100 - 179; return 0.13117165798208050667e0 + (-0.68512309830281084723e-2 + (0.28147075431133863774e-4 + (0.72351212437979583441e-7 + (-0.23927816200314358570e-8 + (0.24345469651209833155e-10 - 0.16319736960000000000e-12 * t) * t) * t) * t) * t) * t; } case 90: { - double t = 2*y100 - 181; + double t = 2 * y100 - 181; return 0.11758232561160626306e0 + (-0.67378491192463392927e-2 + (0.28525664781722907847e-4 + (0.54156999310046790024e-7 + (-0.21589405340123827823e-8 + (0.22444150951727334619e-10 - 0.15368675584000000000e-12 * t) * t) * t) * t) * t) * t; } case 91: { - double t = 2*y100 - 183; + double t = 2 * y100 - 183; return 0.10422112945361673560e0 + (-0.66231638959845581564e-2 + (0.28800551216363918088e-4 + (0.37758983397952149613e-7 + (-0.19435423557038933431e-8 + (0.20656766125421362458e-10 - 0.14422990012444444444e-12 * t) * t) * t) * t) * t) * t; } case 92: { - double t = 2*y100 - 185; + double t = 2 * y100 - 185; return 0.91090275493541084785e-1 + (-0.65075691516115160062e-2 + (0.28982078385527224867e-4 + (0.23014165807643012781e-7 + (-0.17454532910249875958e-8 + (0.18981946442680092373e-10 - 0.13494234691555555556e-12 * t) * t) * t) * t) * t) * t; } case 93: { - double t = 2*y100 - 187; + double t = 2 * y100 - 187; return 0.78191222288771379358e-1 + (-0.63914190297303976434e-2 + (0.29079759021299682675e-4 + (0.97885458059415717014e-8 + (-0.15635596116134296819e-8 + (0.17417110744051331974e-10 - 0.12591151763555555556e-12 * t) * t) * t) * t) * t) * t; } case 94: { - double t = 2*y100 - 189; + double t = 2 * y100 - 189; return 0.65524757106147402224e-1 + (-0.62750311956082444159e-2 + (0.29102328354323449795e-4 + (-0.20430838882727954582e-8 + (-0.13967781903855367270e-8 + (0.15958771833747057569e-10 - 0.11720175765333333333e-12 * t) * t) * t) * t) * t) * t; } case 95: { - double t = 2*y100 - 191; + double t = 2 * y100 - 191; return 0.53091065838453612773e-1 + (-0.61586898417077043662e-2 + (0.29057796072960100710e-4 + (-0.12597414620517987536e-7 + (-0.12440642607426861943e-8 + (0.14602787128447932137e-10 - 0.10885859114666666667e-12 * t) * t) * t) * t) * t) * t; } case 96: { - double t = 2*y100 - 193; + double t = 2 * y100 - 193; return 0.40889797115352738582e-1 + (-0.60426484889413678200e-2 + (0.28953496450191694606e-4 + (-0.21982952021823718400e-7 + (-0.11044169117553026211e-8 + (0.13344562332430552171e-10 - 0.10091231402844444444e-12 * t) * t) * t) * t) * t) * t; } @@ -588,17 +592,13 @@ double EvaluateWImY100Function(double y100, double x) { // use Taylor expansion for small x (|x| <= 0.0309...) // (2/sqrt(pi)) * (x - 2/3 x^3 + 4/15 x^5 - 8/105 x^7 + 16/945 x^9) - double x2 = x*x; - return x * (1.1283791670955125739 - - x2 * (0.75225277806367504925 - - x2 * (0.30090111122547001970 - - x2 * (0.085971746064420005629 - - x2 * 0.016931216931216931217)))); + double x2 = x * x; + return x * (1.1283791670955125739 - x2 * (0.75225277806367504925 - x2 * (0.30090111122547001970 - x2 * (0.085971746064420005629 - x2 * 0.016931216931216931217)))); + } } + /* Since 0 <= y100 < 101, this is only reached if x is NaN, + in which case we should return NaN. */ + return std::numeric_limits::quiet_NaN(); } - /* Since 0 <= y100 < 101, this is only reached if x is NaN, - in which case we should return NaN. */ - return std::numeric_limits::quiet_NaN(); -} } // end of namespace anima diff --git a/Anima/math-tools/special_functions/animaErrorFunctions.h b/Anima/math-tools/special_functions/animaErrorFunctions.h index a39ec512f..b868894b5 100644 --- a/Anima/math-tools/special_functions/animaErrorFunctions.h +++ b/Anima/math-tools/special_functions/animaErrorFunctions.h @@ -1,34 +1,35 @@ #pragma once #include "AnimaSpecialFunctionsExport.h" + #include namespace anima { -class DawsonIntegrand -{ -public: - void SetXValue(double val) {m_XValue = val;} - - double operator() (const double t) + class DawsonIntegrand { - double inexpValue = m_XValue * m_XValue * (t * t - 1.0); - return std::exp(inexpValue); - } - -private: - double m_XValue; -}; + public: + void SetXValue(double val) { m_XValue = val; } + + double operator()(const double t) + { + double inexpValue = m_XValue * m_XValue * (t * t - 1.0); + return std::exp(inexpValue); + } + + private: + double m_XValue; + }; -ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonIntegral(const double x, const bool scaled = false); + ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonIntegral(const double x, const bool scaled = false); -ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonFunctionNR(double x); + ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonFunctionNR(double x); -ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonFunction(double x); + ANIMASPECIALFUNCTIONS_EXPORT double EvaluateDawsonFunction(double x); -ANIMASPECIALFUNCTIONS_EXPORT double EvaluateWImFunction(double x); + ANIMASPECIALFUNCTIONS_EXPORT double EvaluateWImFunction(double x); -ANIMASPECIALFUNCTIONS_EXPORT double EvaluateWImY100Function(double y100, double x); + ANIMASPECIALFUNCTIONS_EXPORT double EvaluateWImY100Function(double y100, double x); } // end namespace anima diff --git a/Anima/math-tools/special_functions/animaGammaFunctions.cxx b/Anima/math-tools/special_functions/animaGammaFunctions.cxx index 703e2b267..89158b066 100644 --- a/Anima/math-tools/special_functions/animaGammaFunctions.cxx +++ b/Anima/math-tools/special_functions/animaGammaFunctions.cxx @@ -2,24 +2,87 @@ #include "animaGammaFunctions.h" #include +#include +#include #include namespace anima { -double psi_function(unsigned int n, double emc) +double psi_function(unsigned int n) { if (n < 1) throw itk::ExceptionObject(__FILE__, __LINE__,"The Psi function is not defined in 0.",ITK_LOCATION); - double resVal = - emc; + double resVal = digamma(1.0); for (unsigned int i = 1;i < n;++i) resVal += 1.0 / ((double)i); return resVal; } +double digamma(const double x) +{ + if (x >= 600.0) + return std::log(x - 0.5); + + if (x < 1.0e-4) + { + double gammaValue = -boost::math::digamma(1.0); + return -1.0 / x - gammaValue; + } + + return boost::math::digamma(x); +} + +double trigamma(const double x) +{ + return boost::math::trigamma(x); +} + +double inverse_digamma(const double x) +{ + if (x > digamma(600.0)) + return std::exp(x) + 0.5; + + if (x < digamma(1.0e-4)) + { + double gammaValue = -boost::math::digamma(1.0); + return -1.0 / (x + gammaValue); + } + + double gammaValue = -digamma(1.0); + double resValue = 0.0; + + if (x < -2.22) + resValue = -1.0 / (x + gammaValue); + else + resValue = std::exp(x) + 0.5; + + bool continueLoop = true; + while (continueLoop) + { + double oldResValue = resValue; + double residualValue = digamma(resValue) - x; + if (std::abs(residualValue) < std::sqrt(std::numeric_limits::epsilon())) + break; + double stepValue = resValue / 2.0; // bisection + double denomValue = trigamma(resValue); + if (std::abs(denomValue) > std::sqrt(std::numeric_limits::epsilon())) + { + double tmpStepValue = residualValue / denomValue; + if (tmpStepValue < resValue) + stepValue = tmpStepValue; + } + + resValue -= stepValue; + continueLoop = std::abs(resValue - oldResValue) > std::sqrt(std::numeric_limits::epsilon()); + } + + return resValue; +} + double gammaHalfPlusN(unsigned int n) { double resVal = std::tgamma(2*n + 1); diff --git a/Anima/math-tools/special_functions/animaGammaFunctions.h b/Anima/math-tools/special_functions/animaGammaFunctions.h index 124f688f4..fce41f3ea 100644 --- a/Anima/math-tools/special_functions/animaGammaFunctions.h +++ b/Anima/math-tools/special_functions/animaGammaFunctions.h @@ -5,9 +5,12 @@ namespace anima { - ANIMASPECIALFUNCTIONS_EXPORT double psi_function(unsigned int n, double emc); + ANIMASPECIALFUNCTIONS_EXPORT double psi_function(unsigned int n); ANIMASPECIALFUNCTIONS_EXPORT double gammaHalfPlusN(unsigned int n); ANIMASPECIALFUNCTIONS_EXPORT double gammaHalfMinusN(unsigned int n); + ANIMASPECIALFUNCTIONS_EXPORT double digamma(const double x); + ANIMASPECIALFUNCTIONS_EXPORT double trigamma(const double x); + ANIMASPECIALFUNCTIONS_EXPORT double inverse_digamma(const double x); } // end of namespace anima diff --git a/Anima/math-tools/special_functions/animaKummerFunctions.cxx b/Anima/math-tools/special_functions/animaKummerFunctions.cxx index fbc278569..501ec230b 100644 --- a/Anima/math-tools/special_functions/animaKummerFunctions.cxx +++ b/Anima/math-tools/special_functions/animaKummerFunctions.cxx @@ -1,146 +1,154 @@ -#include +#include "animaKummerFunctions.h" + #include +#include + #include namespace anima { -double -PochHammer(const double &x, - const unsigned int n) -{ - double resVal = 1.0; - - for (unsigned int i = 0;i < n;++i) - resVal *= (x + i); - - return resVal; -} - -double -KummerMethod1(const double &x, const double &a, const double &b, - const unsigned int maxIter, const double tol) -{ - double resVal = 1.0; - - bool stopLoop = false; - unsigned int counter = 2; + double + PochHammer(const double &x, + const unsigned int n) + { + double resVal = 1.0; - std::vector aVals(2); - aVals[0] = 1; - aVals[1] = 1.0 + x * a / b; + for (unsigned int i = 0; i < n; ++i) + resVal *= (x + i); - while (counter < maxIter && !stopLoop) + return resVal; + } + + double + KummerMethod1(const double &x, const double &a, const double &b, + const unsigned int maxIter, const double tol) { - double r = (a + counter - 1.0) / (counter * (b + counter - 1.0)); - double newVal = aVals[1] + (aVals[1] - aVals[0]) * r * x; + double resVal = 1.0; - if ((counter != 2) && (std::abs(newVal - resVal) < tol * std::abs(resVal))) - stopLoop = true; - else + bool stopLoop = false; + unsigned int counter = 2; + + std::vector aVals(2); + aVals[0] = 1; + aVals[1] = 1.0 + x * a / b; + + while (counter < maxIter && !stopLoop) { - resVal = newVal; - aVals[0] = aVals[1]; - aVals[1] = newVal; + double r = (a + counter - 1.0) / (counter * (b + counter - 1.0)); + double newVal = aVals[1] + (aVals[1] - aVals[0]) * r * x; + + if ((counter != 2) && (std::abs(newVal - resVal) < tol * std::abs(resVal))) + stopLoop = true; + else + { + resVal = newVal; + aVals[0] = aVals[1]; + aVals[1] = newVal; + } + + ++counter; } - ++counter; + return resVal; } - - return resVal; -} -double -KummerMethod2(const double &x, const double &a, const double &b, - const unsigned int maxIter, const double tol) -{ - double resVal = 1.0; - - bool stopLoop = false; - unsigned int counter = 0; - double factorial_counter = 1; - - while (counter < maxIter && !stopLoop) + double + KummerMethod2(const double &x, const double &a, const double &b, + const unsigned int maxIter, const double tol) { - ++counter; - factorial_counter *= counter; - - double tmpVal = resVal; - - double poch_a, poch_b; - - if (x > 0) + double resVal = 1.0; + + bool stopLoop = false; + unsigned int counter = 0; + double factorial_counter = 1; + + while (counter < maxIter && !stopLoop) { - poch_a = PochHammer(1.0 - a,counter); - poch_b = PochHammer(b - a,counter); + ++counter; + factorial_counter *= counter; + + double tmpVal = resVal; + + double poch_a, poch_b; + + if (x > 0) + { + poch_a = PochHammer(1.0 - a, counter); + poch_b = PochHammer(b - a, counter); + } + else + { + poch_a = PochHammer(a, counter); + poch_b = PochHammer(1.0 + a - b, counter); + } + + resVal += poch_a * poch_b * std::pow(std::abs(x), -1.0 * counter) / factorial_counter; + + if ((counter != 1) && (std::abs(resVal - tmpVal) < tol * std::abs(tmpVal))) + stopLoop = true; } + + if (x > 0) + resVal *= std::exp(x) * std::pow(x, (double)(a - b)) / std::tgamma(a); else - { - poch_a = PochHammer(a,counter); - poch_b = PochHammer(1.0 + a - b,counter); - } - - resVal += poch_a * poch_b * std::pow(std::abs(x),-1.0 * counter) / factorial_counter; - - if ((counter != 1) && (std::abs(resVal - tmpVal) < tol * std::abs(tmpVal))) - stopLoop = true; - } - - if (x > 0) - resVal *= std::exp(x) * std::pow(x,(double)(a-b)) / std::tgamma(a); - else - resVal *= std::pow(-x,-1.0 * a) / std::tgamma(b-a); - - resVal *= std::tgamma(b); - - return resVal; -} - -double KummerIntegrandMethod(const double &x, const double &a, const double &b) -{ - KummerIntegrand integrand; - integrand.SetXValue(x); - integrand.SetAValue(a); - integrand.SetBValue(b); - double resVal = boost::math::quadrature::gauss::integrate(integrand, 0.0, 1.0); - resVal *= std::tgamma(b) / std::tgamma(a) / std::tgamma(b - a); - - return resVal; -} - -double -GetKummerFunctionValue(const double &x, const double &a, const double &b, - const unsigned int maxIter, const double tol) -{ - if ((a == 0) || (x == 0)) - return 1.0; + resVal *= std::pow(-x, -1.0 * a) / std::tgamma(b - a); - if (a == b) - return std::exp(x); + resVal *= std::tgamma(b); - if (a > 0 && b > a) + return resVal; + } + + double KummerIntegrandMethod(const double &x, const double &a, const double &b) { - double resVal = KummerIntegrandMethod(x, a, b); + KummerIntegrand integrand; + integrand.SetXValue(x); + integrand.SetAValue(a); + integrand.SetBValue(b); - if (x > 0) - resVal = std::exp(x + std::log(resVal)); + auto f1 = [&integrand](double t) + { return integrand(t); }; + + double resVal = boost::math::quadrature::gauss_kronrod::integrate(f1, 0.0, 1.0); + + double logMultConst = std::lgamma(b) - std::lgamma(a) - std::lgamma(b - a); + resVal *= std::exp(logMultConst); return resVal; } - double rFactor = std::abs(x * a / b); - if (rFactor < 20.0) - return KummerMethod1(x, a, b, maxIter, tol); - else + double + GetKummerFunctionValue(const double &x, const double &a, const double &b, + const unsigned int maxIter, const double tol) { + if ((a == 0) || (x == 0)) + return 1.0; + + if (a == b) + return std::exp(x); + + if (a > 0 && b > a) + { + double resVal = KummerIntegrandMethod(x, a, b); + + if (x > 0) + resVal = std::exp(x + std::log(resVal)); + + return resVal; + } + + double rFactor = std::abs(x * a / b); + if (rFactor < 20.0) + return KummerMethod1(x, a, b, maxIter, tol); + if (a > b) { double ap = b - a; double xp = -x; if (ap > b) - throw itk::ExceptionObject(__FILE__, __LINE__,"Invalid inputs for Kummer function using method 2",ITK_LOCATION); + throw itk::ExceptionObject(__FILE__, __LINE__, "Invalid inputs for Kummer function using method 2", ITK_LOCATION); - double tmpVal = KummerMethod2(xp,ap,b,maxIter,tol); + double tmpVal = KummerMethod2(xp, ap, b, maxIter, tol); double logResVal = x + std::log(std::abs(tmpVal)); double factor = (0.0 < tmpVal) - (0.0 > tmpVal); return factor * std::exp(logResVal); @@ -148,45 +156,42 @@ GetKummerFunctionValue(const double &x, const double &a, const double &b, return KummerMethod2(x, a, b, maxIter, tol); } -} -double -GetScaledKummerFunctionValue(const double &x, const double &a, const double &b, - const unsigned int maxIter, const double tol) -{ - if ((a == 0) || (x == 0)) - return std::exp(-x); + double + GetScaledKummerFunctionValue(const double &x, const double &a, const double &b, + const unsigned int maxIter, const double tol) + { + if ((a == 0) || (x == 0)) + return std::exp(-x); - if (a == b) - return 1.0; + if (a == b) + return 1.0; - if (a > 0 && b > a) - { - double resVal = KummerIntegrandMethod(x, a, b); + if (a > 0 && b > a) + { + double resVal = KummerIntegrandMethod(x, a, b); - if (x < 0) - resVal = std::exp(- x + std::log(resVal)); + if (x < 0) + resVal = std::exp(-x + std::log(resVal)); - return resVal; - } + return resVal; + } + + double rFactor = std::abs(x * a / b); + if (rFactor < 20.0) + return std::exp(-x + std::log(KummerMethod1(x, a, b, maxIter, tol))); - double rFactor = std::abs(x * a / b); - if (rFactor < 20.0) - return std::exp(-x + std::log(KummerMethod1(x, a, b, maxIter, tol))); - else - { if (a > b) { double ap = b - a; double xp = -x; if (ap > b) - throw itk::ExceptionObject(__FILE__, __LINE__,"Invalid inputs for Kummer function using method 2",ITK_LOCATION); + throw itk::ExceptionObject(__FILE__, __LINE__, "Invalid inputs for Kummer function using method 2", ITK_LOCATION); return KummerMethod2(xp, ap, b, maxIter, tol); } return std::exp(-x + std::log(KummerMethod2(x, a, b, maxIter, tol))); } -} } // end of namespace anima diff --git a/Anima/math-tools/special_functions/animaKummerFunctions.h b/Anima/math-tools/special_functions/animaKummerFunctions.h index 3bd83f00c..f3554e78d 100644 --- a/Anima/math-tools/special_functions/animaKummerFunctions.h +++ b/Anima/math-tools/special_functions/animaKummerFunctions.h @@ -1,60 +1,61 @@ #pragma once #include "AnimaSpecialFunctionsExport.h" + #include namespace anima { -ANIMASPECIALFUNCTIONS_EXPORT -double PochHammer(const double &x, - const unsigned int n); - -//! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method 1.C, p.5 -ANIMASPECIALFUNCTIONS_EXPORT -double -KummerMethod1(const double &x, const double &a, const double &b, - const unsigned int maxIter = 1000, const double tol = 1.0e-8); - -//! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method 2, p.6 -ANIMASPECIALFUNCTIONS_EXPORT -double -KummerMethod2(const double &x, const double &a, const double &b, - const unsigned int maxIter = 1000, const double tol = 1.0e-8); - -//! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method with integral if b > a > 0 -ANIMASPECIALFUNCTIONS_EXPORT -double -KummerIntegrandMethod(const double &x, const double &a, const double &b); - -//! Computes the confluent hypergeometric function 1F1 also known as the Kummer function M. It calls Kummer function core to get the value. -ANIMASPECIALFUNCTIONS_EXPORT -double -GetKummerFunctionValue(const double &x, const double &a, const double &b, - const unsigned int maxIter = 1000, const double tol = 1.0e-8); - -//! Computes the confluent hypergeometric function 1F1 also known as the Kummer function M. It returns a scaled value: exp(-x) * M(x,a,b). -ANIMASPECIALFUNCTIONS_EXPORT -double -GetScaledKummerFunctionValue(const double &x, const double &a, const double &b, - const unsigned int maxIter = 1000, const double tol = 1.0e-8); - -class KummerIntegrand -{ -public: - void SetXValue(double val) {m_XValue = val;} - void SetAValue(double val) {m_AValue = val;} - void SetBValue(double val) {m_BValue = val;} - - double operator() (const double t) + ANIMASPECIALFUNCTIONS_EXPORT + double PochHammer(const double &x, + const unsigned int n); + + //! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method 1.C, p.5 + ANIMASPECIALFUNCTIONS_EXPORT + double + KummerMethod1(const double &x, const double &a, const double &b, + const unsigned int maxIter = 1000, const double tol = 1.0e-8); + + //! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method 2, p.6 + ANIMASPECIALFUNCTIONS_EXPORT + double + KummerMethod2(const double &x, const double &a, const double &b, + const unsigned int maxIter = 1000, const double tol = 1.0e-8); + + //! According to Muller, K. E. (2001) ‘Computing the confluent hypergeometric function, M (a, b, x)’, Numerische Mathematik, pp. 179–196. Method with integral if b > a > 0 + ANIMASPECIALFUNCTIONS_EXPORT + double + KummerIntegrandMethod(const double &x, const double &a, const double &b); + + //! Computes the confluent hypergeometric function 1F1 also known as the Kummer function M. It calls Kummer function core to get the value. + ANIMASPECIALFUNCTIONS_EXPORT + double + GetKummerFunctionValue(const double &x, const double &a, const double &b, + const unsigned int maxIter = 1000, const double tol = 1.0e-8); + + //! Computes the confluent hypergeometric function 1F1 also known as the Kummer function M. It returns a scaled value: exp(-x) * M(x,a,b). + ANIMASPECIALFUNCTIONS_EXPORT + double + GetScaledKummerFunctionValue(const double &x, const double &a, const double &b, + const unsigned int maxIter = 1000, const double tol = 1.0e-8); + + class KummerIntegrand { - // For better numerical stability - double tModified = t - (m_XValue > 0.0); - return std::exp(m_XValue * tModified) * std::pow(t, m_AValue - 1.0) * std::pow(1.0 - t, m_BValue - m_AValue - 1.0); - } - -private: - double m_XValue, m_AValue, m_BValue; -}; + public: + void SetXValue(double val) { m_XValue = val; } + void SetAValue(double val) { m_AValue = val; } + void SetBValue(double val) { m_BValue = val; } + + double operator()(const double t) + { + // For better numerical stability + double tModified = t - (m_XValue > 0.0); + return std::exp(m_XValue * tModified) * std::pow(t, m_AValue - 1.0) * std::pow(1.0 - t, m_BValue - m_AValue - 1.0); + } + + private: + double m_XValue, m_AValue, m_BValue; + }; } // end namespace anima diff --git a/Anima/math-tools/special_functions/dawson_integral_test/CMakeLists.txt b/Anima/math-tools/special_functions/dawson_integral_test/CMakeLists.txt new file mode 100644 index 000000000..6af896eda --- /dev/null +++ b/Anima/math-tools/special_functions/dawson_integral_test/CMakeLists.txt @@ -0,0 +1,29 @@ +if(BUILD_TESTING) + + project(animaDawsonIntegralTest) + + # ############################################################################ + # List Sources + # ############################################################################ + + list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) + + # ############################################################################ + # add executable + # ############################################################################ + + add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) + + # ############################################################################ + # Link + # ############################################################################ + + target_link_libraries(${PROJECT_NAME} AnimaSpecialFunctions) + + # ############################################################################ + # install + # ############################################################################ + + set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/special_functions/dawson_integral_test/animaDawsonIntegralTest.cxx b/Anima/math-tools/special_functions/dawson_integral_test/animaDawsonIntegralTest.cxx new file mode 100644 index 000000000..1c2228e89 --- /dev/null +++ b/Anima/math-tools/special_functions/dawson_integral_test/animaDawsonIntegralTest.cxx @@ -0,0 +1,122 @@ +#include + +#include + +#include + +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg lbArg( + "l", "lower-bound", + "A numeric value specifying the lower bound of the domain (default: -5.0).", + false, -5.0, "lower bound", cmd); + TCLAP::ValueArg ubArg( + "u", "upper-bound", + "A numeric value specifying the upper bound of the domain (default: +5.0).", + false, 5.0, "upper bound", cmd); + TCLAP::ValueArg nPtsArg( + "n", "nb-points", + "An integer value specifying the size of the grid on which the domain is discretized (default: 1000).", + false, 10000, "grid size", cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException &e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + double lbValue = lbArg.getValue(); + double ubValue = ubArg.getValue(); + + if (ubValue < lbValue) + { + std::cerr << "The lower bound should not exceed the upper bound." << std::endl; + return EXIT_FAILURE; + } + + unsigned int nbValues = nPtsArg.getValue(); + double stepValue = (ubValue - lbValue) / (static_cast(nbValues) - 1.0); + + std::vector inputValues(nbValues); + std::vector outputValues1(nbValues); + std::vector outputValues2(nbValues); + std::vector outputValues3(nbValues); + + double value = lbValue; + for (unsigned int i = 0; i < nbValues; ++i) + { + inputValues[i] = value; + value += stepValue; + } + + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + + try + { + for (unsigned int i = 0; i < nbValues; ++i) + outputValues1[i] = anima::EvaluateDawsonIntegral(inputValues[i]); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "Dawson integral computed in " << tmpTimer.GetTotal() << " s" << std::endl; + + tmpTimer.Start(); + + try + { + for (unsigned int i = 0; i < nbValues; ++i) + outputValues2[i] = anima::EvaluateDawsonFunctionNR(inputValues[i]); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "EvaluateDawsonFunctionNR computed in " << tmpTimer.GetTotal() << " s" << std::endl; + + tmpTimer.Start(); + + try + { + for (unsigned int i = 0; i < nbValues; ++i) + outputValues3[i] = anima::EvaluateDawsonFunction(inputValues[i]); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "EvaluateDawsonFunction computed in " << tmpTimer.GetTotal() << " s" << std::endl; + + std::ofstream myfile; + myfile.open("/Users/stamm-a/Downloads/example.csv"); + myfile << "x,y1,y2,y3\n"; + for (unsigned int i = 0; i < nbValues; ++i) + myfile << std::to_string(inputValues[i]) << "," << std::to_string(outputValues1[i]) << "," << std::to_string(outputValues2[i]) << "," << std::to_string(outputValues3[i]) << "\n"; + myfile.close(); + + return EXIT_SUCCESS; +} diff --git a/Anima/math-tools/special_functions/kummer_function_test/CMakeLists.txt b/Anima/math-tools/special_functions/kummer_function_test/CMakeLists.txt new file mode 100644 index 000000000..eb5472fa3 --- /dev/null +++ b/Anima/math-tools/special_functions/kummer_function_test/CMakeLists.txt @@ -0,0 +1,29 @@ +if(BUILD_TESTING) + + project(animaKummerFunctionTest) + + # ############################################################################ + # List Sources + # ############################################################################ + + list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) + + # ############################################################################ + # add executable + # ############################################################################ + + add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) + + # ############################################################################ + # Link + # ############################################################################ + + target_link_libraries(${PROJECT_NAME} AnimaSpecialFunctions) + + # ############################################################################ + # install + # ############################################################################ + + set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/special_functions/kummer_function_test/animaKummerFunctionTest.cxx b/Anima/math-tools/special_functions/kummer_function_test/animaKummerFunctionTest.cxx new file mode 100644 index 000000000..72bf4fc25 --- /dev/null +++ b/Anima/math-tools/special_functions/kummer_function_test/animaKummerFunctionTest.cxx @@ -0,0 +1,103 @@ +#include + +#include + +#include + +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg lbArg( + "l", "lower-bound", + "A numeric value specifying the lower bound of the domain (default: -5.0).", + false, -5.0, "lower bound", cmd); + TCLAP::ValueArg ubArg( + "u", "upper-bound", + "A numeric value specifying the upper bound of the domain (default: +5.0).", + false, 5.0, "upper bound", cmd); + TCLAP::ValueArg nPtsArg( + "n", "nb-points", + "An integer value specifying the size of the grid on which the domain is discretized (default: 1000).", + false, 10000, "grid size", cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException &e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + double lbValue = lbArg.getValue(); + double ubValue = ubArg.getValue(); + + if (ubValue < lbValue) + { + std::cerr << "The lower bound should not exceed the upper bound." << std::endl; + return EXIT_FAILURE; + } + + unsigned int nbValues = nPtsArg.getValue(); + double stepValue = (ubValue - lbValue) / (static_cast(nbValues) - 1.0); + + std::vector inputValues(nbValues); + std::vector outputValues1(nbValues); + std::vector outputValues2(nbValues); + + double value = lbValue; + for (unsigned int i = 0; i < nbValues; ++i) + { + inputValues[i] = value; + value += stepValue; + } + + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + + try + { + for (unsigned int i = 0; i < nbValues; ++i) + outputValues1[i] = anima::GetKummerFunctionValue(inputValues[i], 0.5, 1.5); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "GetKummerFunctionValue() computed in " << tmpTimer.GetTotal() << " s" << std::endl; + + tmpTimer.Start(); + + try + { + for (unsigned int i = 0; i < nbValues; ++i) + outputValues2[i] = anima::GetScaledKummerFunctionValue(inputValues[i], 0.5, 1.5); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "GetScaledKummerFunctionValue() computed in " << tmpTimer.GetTotal() << " s" << std::endl; + std::ofstream myfile; + myfile.open("/Users/stamm-a/Downloads/example.csv"); + myfile << "x,y1,y2\n"; + for (unsigned int i = 0; i < nbValues; ++i) + myfile << std::to_string(inputValues[i]) << "," << std::to_string(outputValues1[i]) << "," << std::to_string(outputValues2[i]) << "\n"; + myfile.close(); + + return EXIT_SUCCESS; +} diff --git a/Anima/math-tools/sphere_operations/sphere_average_test/CMakeLists.txt b/Anima/math-tools/sphere_operations/sphere_average_test/CMakeLists.txt index c5901fc2d..fabce834d 100644 --- a/Anima/math-tools/sphere_operations/sphere_average_test/CMakeLists.txt +++ b/Anima/math-tools/sphere_operations/sphere_average_test/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} + AnimaStatisticalDistributions ) ## ############################################################################# diff --git a/Anima/math-tools/sphere_operations/sphere_average_test/animaSphereAverageTest.cxx b/Anima/math-tools/sphere_operations/sphere_average_test/animaSphereAverageTest.cxx index 4713ddb61..4ea9a16a4 100644 --- a/Anima/math-tools/sphere_operations/sphere_average_test/animaSphereAverageTest.cxx +++ b/Anima/math-tools/sphere_operations/sphere_average_test/animaSphereAverageTest.cxx @@ -1,70 +1,77 @@ #include -#include - -#include +#include +#include #include -int main(int argc, char **argv) +#include + +int main(int argc, char **argv) { - TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); - TCLAP::ValueArg numPointsArg("n","nb-points","Number of points (default: 20)",false,20,"number of points",cmd); - TCLAP::SwitchArg equalWeightsArg("E", "equal-weights", "Use equal weights",cmd,false); + TCLAP::ValueArg numPointsArg("n", "nb-points", "Number of points (default: 20)", false, 20, "number of points", cmd); + TCLAP::SwitchArg equalWeightsArg("E", "equal-weights", "Use equal weights", cmd, false); try { - cmd.parse(argc,argv); + cmd.parse(argc, argv); } - catch (TCLAP::ArgException& e) + catch (TCLAP::ArgException &e) { std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; return EXIT_FAILURE; } - typedef std::mt19937 RandomGeneratorType; + using RandomGeneratorType = std::mt19937; RandomGeneratorType generator(time(0)); // This is what will actually supply drawn values. - std::vector < std::vector > random_sphere_points(numPointsArg.getValue()); - std::vector random_sphere_point(3,0); - std::vector average_point(3); - std::vector weights(numPointsArg.getValue()); - - for (unsigned int i = 0;i < numPointsArg.getValue();++i) + using SphericalUniformDistribution = anima::SphericalUniformDistribution; + SphericalUniformDistribution sphUnifDistr; + SphericalUniformDistribution::SampleType sphUnifSample(numPointsArg.getValue()); + sphUnifDistr.Random(sphUnifSample, generator); + std::vector> random_sphere_points(numPointsArg.getValue()); + std::vector random_sphere_point(3, 0); + std::vector average_point(3); + std::vector weights(numPointsArg.getValue()); + + for (unsigned int i = 0; i < numPointsArg.getValue(); ++i) { - anima::SampleFromUniformDistributionOn2Sphere(generator,random_sphere_point); - random_sphere_points[i] = random_sphere_point; + random_sphere_points[i].resize(3); + for (unsigned int j = 0; j < 3; ++j) + random_sphere_points[i][j] = sphUnifSample[i][j]; } if (equalWeightsArg.isSet()) { - for (unsigned int i = 0;i < numPointsArg.getValue();++i) + for (unsigned int i = 0; i < numPointsArg.getValue(); ++i) weights[i] = 1.0 / numPointsArg.getValue(); } else { - double sumWeights = 0; + anima::RealUniformDistribution unifDistr; + unifDistr.SetLowerBoundParameter(0.01); + unifDistr.SetUpperBoundParameter(100.0); + unifDistr.Random(weights, generator); - for (unsigned int i = 0;i < numPointsArg.getValue();++i) - { - weights[i] = anima::SampleFromUniformDistribution(0.01,100.0,generator); + double sumWeights = 0; + for (unsigned int i = 0; i < numPointsArg.getValue(); ++i) sumWeights += weights[i]; - } - for (unsigned int i = 0;i < numPointsArg.getValue();++i) + for (unsigned int i = 0; i < numPointsArg.getValue(); ++i) weights[i] /= sumWeights; } - anima::ComputeSphericalCentroid(random_sphere_points,average_point,random_sphere_points[0],weights); + anima::ComputeSphericalCentroid(random_sphere_points, average_point, random_sphere_points[0], weights); std::cout << "Initial points: " << std::endl; - for (unsigned int i = 0;i < numPointsArg.getValue();++i) + for (unsigned int i = 0; i < numPointsArg.getValue(); ++i) std::cout << random_sphere_points[i][0] << " " << random_sphere_points[i][1] << " " << random_sphere_points[i][2] << std::endl; std::cout << "Weights: "; - for (unsigned int i = 0;i < weights.size();++i) + for (unsigned int i = 0; i < weights.size(); ++i) std::cout << weights[i] << " "; std::cout << std::endl; diff --git a/Anima/math-tools/statistical_distributions/CMakeLists.txt b/Anima/math-tools/statistical_distributions/CMakeLists.txt index 8d86d9d5d..45351d0dd 100644 --- a/Anima/math-tools/statistical_distributions/CMakeLists.txt +++ b/Anima/math-tools/statistical_distributions/CMakeLists.txt @@ -1,49 +1,38 @@ project(AnimaStatisticalDistributions) -## ############################################################################# -## List Sources -## ############################################################################# +# ############################################################################## +# List Sources +# ############################################################################## -list_source_files(${PROJECT_NAME} - ${CMAKE_CURRENT_SOURCE_DIR} - ) +list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) +# ############################################################################## +# add lib +# ############################################################################## -## ############################################################################# -## add lib -## ############################################################################# +add_library(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) -add_library(${PROJECT_NAME} - ${${PROJECT_NAME}_CFILES} - ) +# ############################################################################## +# Link +# ############################################################################## -## ############################################################################# -## Link -## ############################################################################# +target_link_libraries(${PROJECT_NAME} ITKCommon AnimaSpecialFunctions) -target_link_libraries(${PROJECT_NAME} - ITKCommon - ) - - -################################################################################ +# ############################################################################## # Auto generate the export file for the libs -################################################################################ +# ############################################################################## -generate_export_header(${PROJECT_NAME} - STATIC_DEFINE ${PROJECT_NAME}_BUILT_AS_STATIC - EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/exports/${PROJECT_NAME}Export.h" - ) +generate_export_header( + ${PROJECT_NAME} STATIC_DEFINE ${PROJECT_NAME}_BUILT_AS_STATIC + EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/exports/${PROJECT_NAME}Export.h") - -## ############################################################################# -## install -## ############################################################################# +# ############################################################################## +# install +# ############################################################################## set_lib_install_rules(${PROJECT_NAME}) - -## ############################################################################# -## Add subdirectory for an executable -## ############################################################################# -add_subdirectory(sample_image_from_distribution) \ No newline at end of file +# ############################################################################## +# Add subdirectory for an executable +# ############################################################################## +add_subdirectory(sample_image_from_distribution) diff --git a/Anima/math-tools/statistical_distributions/animaBaseDistribution.cxx b/Anima/math-tools/statistical_distributions/animaBaseDistribution.cxx deleted file mode 100644 index ce78e3f9f..000000000 --- a/Anima/math-tools/statistical_distributions/animaBaseDistribution.cxx +++ /dev/null @@ -1,23 +0,0 @@ -#include "animaBaseDistribution.h" - -#include -#include - -namespace anima -{ - -void BaseDistribution::SetShapeParameter(const double val) -{ - if (val < std::numeric_limits::epsilon()) - throw itk::ExceptionObject(__FILE__, __LINE__, "The shape parameter of a statistical distribution should be strictly positive.", ITK_LOCATION); - m_ShapeParameter = val; -} - -void BaseDistribution::SetScaleParameter(const double val) -{ - if (val < std::numeric_limits::epsilon()) - throw itk::ExceptionObject(__FILE__, __LINE__, "The scale parameter of a statistical distribution should be strictly positive.", ITK_LOCATION); - m_ScaleParameter = val; -} - -} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaBaseDistribution.h b/Anima/math-tools/statistical_distributions/animaBaseDistribution.h index 39117c821..e478da8d1 100644 --- a/Anima/math-tools/statistical_distributions/animaBaseDistribution.h +++ b/Anima/math-tools/statistical_distributions/animaBaseDistribution.h @@ -1,38 +1,38 @@ #pragma once +#include +#include #include #include -#include #include namespace anima { + template class ANIMASTATISTICALDISTRIBUTIONS_EXPORT BaseDistribution { public: + using Self = BaseDistribution; + using ValueType = TValueType; + using SampleType = std::vector; using GeneratorType = std::mt19937; - using VectorType = std::vector; - - BaseDistribution() - { - m_ShapeParameter = 1.0; - m_ScaleParameter = 1.0; - } - void SetShapeParameter(const double val); - double GetShapeParameter() {return m_ShapeParameter;} + BaseDistribution() {} - void SetScaleParameter(const double val); - double GetScaleParameter() {return m_ScaleParameter;} + virtual bool BelongsToSupport(const ValueType &x) = 0; + virtual double GetDensity(const ValueType &x) = 0; + virtual double GetLogDensity(const ValueType &x) = 0; + virtual double GetCumulative(const ValueType &x) = 0; + virtual void Fit(const SampleType &sample, const std::string &method) = 0; + virtual void Random(SampleType &sample, GeneratorType &generator) = 0; + virtual ValueType GetMean() = 0; + virtual double GetVariance() = 0; + virtual double GetDistance(Self *otherDistribution) = 0; - virtual double GetDensity(const double &x) = 0; - virtual double GetLogDensity(const double &x) = 0; - virtual void Fit(const VectorType &sample, const std::string &method) = 0; - virtual void Random(VectorType &sample, GeneratorType &generator) = 0; - - private: - double m_ShapeParameter; - double m_ScaleParameter; + protected: + double GetEpsilon() { return std::sqrt(std::numeric_limits::epsilon()); } }; } // end of namespace + +#include "animaBaseDistribution.hxx" diff --git a/Anima/math-tools/statistical_distributions/animaBaseDistribution.hxx b/Anima/math-tools/statistical_distributions/animaBaseDistribution.hxx new file mode 100644 index 000000000..9019be209 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaBaseDistribution.hxx @@ -0,0 +1,12 @@ +#include "animaBaseDistribution.h" + +#include + +namespace anima +{ + template + double BaseDistribution::GetCumulative(const ValueType &x) + { + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF is not defined for this type of distribution.", ITK_LOCATION); + } +} \ No newline at end of file diff --git a/Anima/math-tools/statistical_distributions/animaDirichletDistribution.cxx b/Anima/math-tools/statistical_distributions/animaDirichletDistribution.cxx new file mode 100644 index 000000000..3404ab19d --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaDirichletDistribution.cxx @@ -0,0 +1,376 @@ +#include "animaDirichletDistribution.h" +#include + +#include + +namespace anima +{ + + bool DirichletDistribution::BelongsToSupport(const ValueType &x) + { + unsigned int numParameters = x.size(); + + double sumValue = 0.0; + for (unsigned int i = 0; i < numParameters; ++i) + { + double tmpValue = x[i]; + if (tmpValue < this->GetEpsilon() || tmpValue > 1.0 - this->GetEpsilon()) + return false; + sumValue += tmpValue; + } + + if (std::abs(sumValue - 1.0) > this->GetEpsilon()) + return false; + + return true; + } + + void DirichletDistribution::SetConcentrationParameters(const std::vector &val) + { + unsigned int numParameters = val.size(); + + for (unsigned int i = 0; i < numParameters; ++i) + { + if (val[i] < this->GetEpsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "The concentration parameters of a statistical distribution should be strictly positive.", ITK_LOCATION); + } + + m_ConcentrationParameters = val; + } + + double DirichletDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + + return std::exp(this->GetLogDensity(x)); + } + + double DirichletDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the Dirichlet distribution is not defined for elements outside the simplex.", ITK_LOCATION); + + unsigned int numParameters = x.size(); + + if (m_ConcentrationParameters.size() != numParameters) + throw itk::ExceptionObject(__FILE__, __LINE__, "The input argument does not belong to the same simplex as the one on which the Dirichlet distribution is defined.", ITK_LOCATION); + + double logBeta = 0.0; + double alphaZero = 0.0; + double resValue = 0.0; + for (unsigned int i = 0; i < numParameters; ++i) + { + logBeta += std::lgamma(m_ConcentrationParameters[i]); + alphaZero += m_ConcentrationParameters[i]; + resValue += (m_ConcentrationParameters[i] - 1.0) * std::log(x[i]); + } + logBeta -= std::lgamma(alphaZero); + resValue -= logBeta; + + return resValue; + } + + double DirichletDistribution::GetCumulative(const ValueType &x) + { + /** + * \fn double GetCumulative(const std::vector &x) + * + * \author Aymeric Stamm (2023). + * + * \param x A numeric vector specifying a point on the support of the Dirichlet distribution. + * + * \return A numeric value storing the value of the CDF of the Dirichlet distribution at point `x`. + * There is no closed-form expression of this function. Hence it is evaluated numerically via + * Monte-Carlo approximation as suggested here: + * https://stats.stackexchange.com/questions/57262/implementation-of-dirichlet-cdf. + */ + + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF of the Dirichlet distribution is not defined for elements outside the simplex.", ITK_LOCATION); + + unsigned int numParameters = x.size(); + + if (m_ConcentrationParameters.size() != numParameters) + throw itk::ExceptionObject(__FILE__, __LINE__, "The input argument does not belong to the same simplex as the one on which the Dirichlet distribution is defined.", ITK_LOCATION); + + const unsigned int numberOfMonteCarloSamples = 10000; + SampleType dirichletSample(numberOfMonteCarloSamples); + GeneratorType generator; + this->Random(dirichletSample, generator); + + double sumValue = 0.0; + for (unsigned int i = 0; i < numberOfMonteCarloSamples; ++i) + { + double sampleValue = 1.0; + for (unsigned int j = 0; j < numParameters; ++j) + { + if (dirichletSample[i][j] > x[j]) + { + sampleValue = 0.0; + break; + } + } + + sumValue += sampleValue; + } + + return sumValue / static_cast(numberOfMonteCarloSamples); + } + + void DirichletDistribution::Fit(const SampleType &sample, const std::string &method) + { + unsigned int numObservations = sample.size(); + unsigned int numParameters = sample[0].size(); + + if (m_ConcentrationParameters.size() != numParameters) + throw itk::ExceptionObject(__FILE__, __LINE__, "The input argument does not belong to the same simplex as the one on which the Dirichlet distribution is defined.", ITK_LOCATION); + + std::vector alphaParameters(numParameters, 1.0); + + m_TotalConcentration = static_cast(numParameters); + m_MeanValues.resize(numParameters); + for (unsigned int i = 0; i < numParameters; ++i) + m_MeanValues[i] = alphaParameters[i] / m_TotalConcentration; + + std::vector usefulValues(numObservations, false); + ValueType sampleValue(numParameters); + unsigned int numUsefulValues = 0; + for (unsigned int i = 0; i < numObservations; ++i) + { + for (unsigned int j = 0; j < numParameters; ++j) + sampleValue[j] = sample[i][j]; + if (this->BelongsToSupport(sampleValue)) + { + usefulValues[i] = true; + numUsefulValues++; + } + } + + if (numUsefulValues < 2) + { + this->SetConcentrationParameters(alphaParameters); + return; + } + + std::vector logParameters(numParameters, 0.0); + for (unsigned int j = 0; j < numParameters; ++j) + { + for (unsigned int i = 0; i < numObservations; ++i) + { + if (!usefulValues[i]) + continue; + logParameters[j] += std::log(sample[i][j]); + } + + logParameters[j] /= static_cast(numUsefulValues); + } + + // Compute initial sum of alpha's + // From https://tminka.github.io/papers/dirichlet/minka-dirichlet.pdf + // there are several options. We pick the one maximizing the first term in Eq. (4) + + // First compute empirical moments of order 1 and 2 + std::vector firstMomentValues(numParameters); + std::vector secondMomentValues(numParameters); + for (unsigned int j = 0; j < numParameters; ++j) + { + double firstMoment = 0.0; + double secondMoment = 0.0; + for (unsigned int i = 0; i < numObservations; ++i) + { + if (!usefulValues[i]) + continue; + double tmpValue = sample[i][j]; + firstMoment += tmpValue; + secondMoment += tmpValue * tmpValue; + } + firstMoment /= static_cast(numUsefulValues); + secondMoment /= static_cast(numUsefulValues); + + firstMomentValues[j] = firstMoment; + secondMomentValues[j] = secondMoment; + } + + double alphaSum = 0.0; + + // Then, try Eq. (23) in https://tminka.github.io/papers/dirichlet/minka-dirichlet.pdf + for (unsigned int j = 0; j < numParameters; ++j) + { + double sumValue = 0.0; + bool skipCandidate = false; + for (unsigned int k = 0; k < numParameters; ++k) + { + if (j == k) + continue; + + double firstMoment = firstMomentValues[k]; + double secondMoment = secondMomentValues[k]; + + double sampleVariance = secondMoment - firstMoment * firstMoment; + if (sampleVariance < this->GetEpsilon()) + { + skipCandidate = true; + break; + } + + double inLogValue = firstMoment * (1.0 - firstMoment) / sampleVariance - 1.0; + if (inLogValue < this->GetEpsilon()) + { + skipCandidate = true; + break; + } + + sumValue += std::log(inLogValue); + } + + if (skipCandidate) + continue; + + double candidateAlphaSum = std::exp(sumValue / (numParameters - 1.0)); + + if (candidateAlphaSum > alphaSum) + alphaSum = candidateAlphaSum; + } + + // Then, try Eq. (21) from https://tminka.github.io/papers/dirichlet/minka-dirichlet.pdf + for (unsigned int j = 0; j < numParameters; ++j) + { + double firstMoment = firstMomentValues[j]; + double secondMoment = secondMomentValues[j]; + + double sampleVariance = secondMoment - firstMoment * firstMoment; + if (sampleVariance < this->GetEpsilon()) + continue; + + double candidateAlphaSum = (firstMoment - secondMoment) / sampleVariance; + if (candidateAlphaSum < this->GetEpsilon()) + continue; + + if (candidateAlphaSum > alphaSum) + alphaSum = candidateAlphaSum; + } + + // Finally, if no solutions, output flat Dirichlet distribution + if (alphaSum < this->GetEpsilon()) + { + this->SetConcentrationParameters(alphaParameters); + return; + } + + double digammaValue = digamma(alphaSum); + + // Fixed point iteration method + // Eq. (9) in https://tminka.github.io/papers/dirichlet/minka-dirichlet.pdf + bool continueLoop = true; + while (continueLoop) + { + double oldDigammaValue = digammaValue; + + for (unsigned int i = 0; i < numParameters; ++i) + { + double psiNew = digammaValue + logParameters[i]; + alphaParameters[i] = anima::inverse_digamma(psiNew); + } + + alphaSum = std::accumulate(alphaParameters.begin(), alphaParameters.end(), 0.0); + digammaValue = digamma(alphaSum); + continueLoop = std::abs(digammaValue - oldDigammaValue) > std::sqrt(std::numeric_limits::epsilon()); + } + + m_TotalConcentration = alphaSum; + for (unsigned int i = 0; i < numParameters; ++i) + m_MeanValues[i] = alphaParameters[i] / m_TotalConcentration; + + this->SetConcentrationParameters(alphaParameters); + } + + void DirichletDistribution::Random(SampleType &sample, GeneratorType &generator) + { + unsigned int numObservations = sample.size(); + unsigned int numParameters = m_ConcentrationParameters.size(); + + for (unsigned int i = 0; i < numObservations; ++i) + sample[i].resize(numParameters); + + BetaDistributionType betaDist; + UniformDistributionType unifDist(0.0, 1.0); + for (unsigned int i = 0; i < numObservations; ++i) + { + double samplePartialSum = 0.0; + for (unsigned int j = 0; j < numParameters - 1; ++j) + { + double alphaPartialSum = 0.0; + for (unsigned int k = j + 1; k < numParameters; ++k) + alphaPartialSum += m_ConcentrationParameters[k]; + + betaDist = BetaDistributionType(m_ConcentrationParameters[j], alphaPartialSum); + double phiValue = boost::math::quantile(betaDist, unifDist(generator)); + + sample[i][j] = (1.0 - samplePartialSum) * phiValue; + samplePartialSum += sample[i][j]; + } + sample[i][numParameters - 1] = 1.0 - samplePartialSum; + } + } + + double DirichletDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double GetDistance(DirichletDistribution *otherDistribution) + * + * \author Aymeric Stamm (2023). + * + * \param otherDistribution A pointer specifying another object of class `DirichletDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence between the current + * Dirichlet distribution and the input Gamma distribution. The implementation follows the formula + * in https://statproofbook.github.io/P/dir-kl. + */ + + std::vector thisConcentrationParams = this->GetConcentrationParameters(); + + DirichletDistribution *dirichletDistr = dynamic_cast(otherDistribution); + std::vector otherConcentrationParams = dirichletDistr->GetConcentrationParameters(); + + unsigned int numParams = thisConcentrationParams.size(); + if (otherConcentrationParams.size() != numParams) + throw itk::ExceptionObject(__FILE__, __LINE__, "The two compared distributions should be on the same support.", ITK_LOCATION); + + double thisSumDigamma = anima::digamma(this->GetTotalConcentration()); + double otherSumDigamma = anima::digamma(dirichletDistr->GetTotalConcentration()); + + double distanceValue = 0.0; + for (unsigned int i = 0; i < numParams; ++i) + { + double thisParam = thisConcentrationParams[i]; + double otherParam = otherConcentrationParams[i]; + double tmpValue = (thisParam - otherParam); + tmpValue *= (anima::digamma(thisParam) - anima::digamma(otherParam) - thisSumDigamma + otherSumDigamma); + distanceValue += tmpValue; + } + + return distanceValue; + } + + vnl_matrix DirichletDistribution::GetCovarianceMatrix() + { + unsigned int numComponents = m_ConcentrationParameters.size(); + vnl_matrix covarianceMatrix(numComponents, numComponents); + + for (unsigned int i = 0; i < numComponents; ++i) + { + covarianceMatrix(i, i) = m_MeanValues[i] * (1.0 - m_MeanValues[i]) / (1.0 + m_TotalConcentration); + + for (unsigned int j = i + 1; j < numComponents; ++j) + { + double tmpValue = -1.0 * m_MeanValues[i] * m_MeanValues[j] / (1.0 + m_TotalConcentration); + covarianceMatrix(i, j) = tmpValue; + covarianceMatrix(j, i) = tmpValue; + } + } + + return covarianceMatrix; + } + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaDirichletDistribution.h b/Anima/math-tools/statistical_distributions/animaDirichletDistribution.h new file mode 100644 index 000000000..4c3ea454c --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaDirichletDistribution.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace anima +{ + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT DirichletDistribution : public BaseDistribution> + { + public: + using UniformDistributionType = std::uniform_real_distribution; + using BetaDistributionType = boost::math::beta_distribution; + + DirichletDistribution() + { + m_ConcentrationParameters.clear(); + m_MeanValues.clear(); + m_TotalConcentration = 0.0; + } + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean() { return m_MeanValues; } + double GetVariance() { return vnl_trace(this->GetCovarianceMatrix()); } + double GetDistance(Self *otherDistribution); + + void SetConcentrationParameters(const std::vector &val); + std::vector GetConcentrationParameters() { return m_ConcentrationParameters; } + + vnl_matrix GetCovarianceMatrix(); + double GetTotalConcentration() { return m_TotalConcentration; } + + private: + std::vector m_ConcentrationParameters; + ValueType m_MeanValues; + double m_TotalConcentration; + }; + +} // end of namespace diff --git a/Anima/math-tools/statistical_distributions/animaDistributionSampling.h b/Anima/math-tools/statistical_distributions/animaDistributionSampling.h deleted file mode 100644 index a16be1754..000000000 --- a/Anima/math-tools/statistical_distributions/animaDistributionSampling.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -namespace anima -{ - -template -double SampleFromUniformDistribution(const T &a, const T &b, std::mt19937 &generator); - -template -void SampleFromUniformDistributionOn2Sphere(std::mt19937 &generator, VectorType &resVec); - -template -unsigned int SampleFromBernoulliDistribution(const T &p, std::mt19937 &generator); - -template -double SampleFromGaussianDistribution(const T &mean, const T &std, std::mt19937 &generator); - -template -void SampleFromMultivariateGaussianDistribution(const VectorType &mean, const vnl_matrix &mat, VectorType &resVec, - std::mt19937 &generator, bool isMatCovariance = true); - -// From Ulrich 1984 -template -void SampleFromVMFDistribution(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, std::mt19937 &generator); - -// From Wenzel 2012 -template -void SampleFromVMFDistributionNumericallyStable(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, std::mt19937 &generator); - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, unsigned int DataDimension, std::mt19937 &generator); - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const vnl_vector_fixed < ScalarType, DataDimension > &meanDirection, vnl_vector_fixed < ScalarType, DataDimension > &resVec, std::mt19937 &generator); - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const itk::Point < ScalarType, DataDimension > &meanDirection, itk::Point < ScalarType, DataDimension > &resVec, std::mt19937 &generator); - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const itk::Vector < ScalarType, DataDimension > &meanDirection, itk::Vector < ScalarType, DataDimension > &resVec, std::mt19937 &generator); - -} // end of namespace anima - -#include "animaDistributionSampling.hxx" diff --git a/Anima/math-tools/statistical_distributions/animaDistributionSampling.hxx b/Anima/math-tools/statistical_distributions/animaDistributionSampling.hxx deleted file mode 100644 index 84efaffb3..000000000 --- a/Anima/math-tools/statistical_distributions/animaDistributionSampling.hxx +++ /dev/null @@ -1,374 +0,0 @@ -#pragma once -#include "animaDistributionSampling.h" - -#include -#include - -#include -#include -#include -#include - -#include - -namespace anima -{ - -template -double SampleFromUniformDistribution(const T &a, const T &b, std::mt19937 &generator) -{ - // Define distribution U[a,b) [double values] - std::uniform_real_distribution uniDbl(a,b); - return uniDbl(generator); -} - -template -void SampleFromUniformDistributionOn2Sphere(std::mt19937 &generator, VectorType &resVec) -{ - std::uniform_real_distribution uniDbl(-1.0,1.0); - double sqSum = 2; - while (sqSum > 1) - { - resVec[0] = uniDbl(generator); - resVec[1] = uniDbl(generator); - - sqSum = resVec[0] * resVec[0] + resVec[1] * resVec[1]; - } - - double factor = 2.0 * std::sqrt(1.0 - sqSum); - resVec[0] *= factor; - resVec[1] *= factor; - resVec[2] = 2.0 * sqSum - 1.0; -} - -template -unsigned int SampleFromBernoulliDistribution(const T &p, std::mt19937 &generator) -{ - std::bernoulli_distribution bernoulli(p); - return bernoulli(generator); -} - -template -double SampleFromGaussianDistribution(const T &mean, const T &std, std::mt19937 &generator) -{ - std::normal_distribution normalDist(mean,std); - return normalDist(generator); -} - -template -void SampleFromMultivariateGaussianDistribution(const VectorType &mean, const vnl_matrix &mat, VectorType &resVec, - std::mt19937 &generator, bool isMatCovariance) -{ - unsigned int vectorSize = mat.rows(); - - vnl_matrix stdMatrix = mat; - - if (isMatCovariance) - { - vnl_matrix eVecs(vectorSize,vectorSize); - vnl_diag_matrix eVals(vectorSize); - - itk::SymmetricEigenAnalysis < vnl_matrix , vnl_diag_matrix, vnl_matrix > eigenComputer(vectorSize); - eigenComputer.ComputeEigenValuesAndVectors(mat, eVals, eVecs); - - for (unsigned int i = 0;i < vectorSize;++i) - eVals[i] = std::sqrt(eVals[i]); - - anima::RecomposeTensor(eVals,eVecs,stdMatrix); - } - - VectorType tmpVec (resVec); - for (unsigned int i = 0;i < vectorSize;++i) - tmpVec[i] = SampleFromGaussianDistribution(0.0,1.0,generator); - - for (unsigned int i = 0;i < vectorSize;++i) - { - resVec[i] = mean[i]; - for (unsigned int j = 0;j < vectorSize;++j) - resVec[i] += stdMatrix(i,j) * tmpVec[j]; - } -} - -template -void SampleFromVMFDistribution(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, std::mt19937 &generator) -{ - VectorType tmpVec; - - for (unsigned int i = 0;i < 3;++i) - { - tmpVec[i] = 0; - resVec[i] = 0; - } - - // Code to compute rotation matrix from vectors, similar to registration code - tmpVec[2] = 1; - - vnl_matrix rotationMatrix = anima::GetRotationMatrixFromVectors(tmpVec,meanDirection).GetVnlMatrix(); - anima::TransformCartesianToSphericalCoordinates(meanDirection, tmpVec); - - if (std::abs(tmpVec[2] - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"Von Mises & Fisher sampling requires mean direction of norm 1.",ITK_LOCATION); - - double tmpVal = std::sqrt(kappa * kappa + 1.0); - double b = (-2.0 * kappa + 2.0 * tmpVal) / 2.0; - double a = (1.0 + kappa + tmpVal) / 2.0; - double d = 4.0 * a * b / (1.0 + b) - 2.0 * anima::safe_log(2.0); - - boost::math::beta_distribution betaDist(1.0, 1.0); - - double T = 1.0; - double U = std::exp(d); - double W = 0; - - while (2.0 * anima::safe_log(T) - T + d < anima::safe_log(U)) - { - double Z = boost::math::quantile(betaDist, SampleFromUniformDistribution(0.0, 1.0, generator)); - U = SampleFromUniformDistribution(0.0, 1.0, generator); - tmpVal = 1.0 - (1.0 - b) * Z; - T = 2.0 * a * b / tmpVal; - W = (1.0 - (1.0 + b) * Z) / tmpVal; - } - - double theta = anima::SampleFromUniformDistribution(0.0, 2.0 * M_PI, generator); - tmpVec[0] = std::sqrt(1.0 - W*W) * std::cos(theta); - tmpVec[1] = std::sqrt(1.0 - W*W) * std::sin(theta); - tmpVec[2] = W; - - for (unsigned int i = 0;i < 3;++i) - for (unsigned int j = 0;j < 3;++j) - resVec[i] += rotationMatrix(i,j) * tmpVec[j]; -} - -template -void SampleFromVMFDistributionNumericallyStable(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, std::mt19937 &generator) -{ - VectorType tmpVec; - - for (unsigned int i = 0;i < 3;++i) - { - tmpVec[i] = 0; - resVec[i] = 0; - } - - // Code to compute rotation matrix from vectors, similar to registration code - tmpVec[2] = 1; - - vnl_matrix rotationMatrix = anima::GetRotationMatrixFromVectors(tmpVec,meanDirection).GetVnlMatrix(); - anima::TransformCartesianToSphericalCoordinates(meanDirection, tmpVec); - - if (std::abs(tmpVec[2] - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"Von Mises & Fisher sampling requires mean direction of norm 1.",ITK_LOCATION); - - double xi = SampleFromUniformDistribution(0.0, 1.0, generator); - double W = 1.0 + (anima::safe_log(xi) + anima::safe_log(1.0 - (xi - 1.0) * exp(-2.0 * kappa) / xi)) / kappa; - double theta = SampleFromUniformDistribution(0.0, 2.0 * M_PI, generator); - - tmpVec[0] = std::sqrt(1.0 - W*W) * std::cos(theta); - tmpVec[1] = std::sqrt(1.0 - W*W) * std::sin(theta); - tmpVec[2] = W; - - for (unsigned int i = 0;i < 3;++i) - for (unsigned int j = 0;j < 3;++j) - resVec[i] += rotationMatrix(i,j) * tmpVec[j]; -} - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const VectorType &meanDirection, VectorType &resVec, unsigned int DataDimension, std::mt19937 &generator) -{ - /**********************************************************************************************//** - * \fn template - * void - * SampleFromWatsonDistribution(const ScalarType &kappa, - * const VectorType &meanDirection, - * VectorType &resVec, - * unsigned int DataDimension, - * std::mt19937 &generator) - * - * \brief Sample from the Watson distribution using the procedure described in - * Fisher et al., Statistical Analysis of Spherical Data, 1993, p.59. - * - * \author Aymeric Stamm - * \date October 2013 - * - * \param kappa Concentration parameter of the Watson distribution. - * \param meanDirection Mean direction of the Watson distribution. - * \param resVec Resulting sample. - * \param DataDimension Dimension of the sphere + 1. - * \param generator Pseudo-random number generator. - **************************************************************************************************/ - - VectorType tmpVec; - - for (unsigned int i = 0;i < DataDimension;++i) - { - tmpVec[i] = 0; - resVec[i] = 0; - } - - // Code to compute rotation matrix from vectors, taken from registration code - tmpVec[2] = 1; - - VectorType rotationNormal; - anima::ComputeCrossProduct(tmpVec, meanDirection, rotationNormal); - anima::Normalize(rotationNormal, rotationNormal); - - // Now resuming onto sampling around direction [0,0,1] - anima::TransformCartesianToSphericalCoordinates(meanDirection, tmpVec); - - double rotationAngle = tmpVec[0]; - - if (std::abs(tmpVec[2] - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"The Watson distribution is on the 2-sphere.",ITK_LOCATION); - - double U, V, S; - if (kappa > 1.0e-6) // Bipolar distribution - { - U = SampleFromUniformDistribution(0.0, 1.0, generator); - S = 1.0 + std::log(U + (1.0 - U) * std::exp(-kappa)) / kappa; - - V = SampleFromUniformDistribution(0.0, 1.0, generator); - - if (V > 1.0e-6) - { - while (std::log(V) > kappa * S * (S - 1.0)) - { - U = SampleFromUniformDistribution(0.0, 1.0, generator); - S = 1.0 + std::log(U + (1.0 - U) * std::exp(-kappa)) / kappa; - - V = SampleFromUniformDistribution(0.0, 1.0, generator); - - if (V < 1.0e-6) - break; - } - } - } - else if (kappa < -1.0e-6) // Gridle distribution - { - double C1 = std::sqrt(std::abs(kappa)); - double C2 = std::atan(C1); - U = SampleFromUniformDistribution(0.0, 1.0, generator); - V = SampleFromUniformDistribution(0.0, 1.0, generator); - S = (1.0 / C1) * std::tan(C2 * U); - - double T = kappa * S * S; - while (V > (1.0 - T) * std::exp(T)) - { - U = SampleFromUniformDistribution(0.0, 1.0, generator); - V = SampleFromUniformDistribution(0.0, 1.0, generator); - S = (1.0 / C1) * std::tan(C2 * U); - T = kappa * S * S; - } - } - else - { - // Sampling uniformly on the sphere - S = std::cos(SampleFromUniformDistribution(0.0, M_PI, generator)); - } - - double phi = SampleFromUniformDistribution(0.0, 2.0 * M_PI, generator); - - tmpVec[0] = std::sqrt(1.0 - S*S) * std::cos(phi); - tmpVec[1] = std::sqrt(1.0 - S*S) * std::sin(phi); - tmpVec[2] = S; - - anima::RotateAroundAxis(tmpVec, rotationAngle, rotationNormal, resVec); - - double resNorm = anima::ComputeNorm(resVec); - - if (std::abs(resNorm - 1.0) > 1.0e-4) - { - std::cout << "Sampled direction norm: " << resNorm << std::endl; - std::cout << "Mean direction: " << meanDirection << std::endl; - std::cout << "Concentration parameter: " << kappa << std::endl; - throw itk::ExceptionObject(__FILE__, __LINE__,"The Watson sampler should generate points on the 2-sphere.",ITK_LOCATION); - } - - anima::Normalize(resVec,resVec); -} - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const vnl_vector_fixed < ScalarType, DataDimension > &meanDirection, vnl_vector_fixed < ScalarType, DataDimension > &resVec, std::mt19937 &generator) -{ - /**********************************************************************************************//** - * \fn template - * void - * SampleFromWatsonDistribution(const ScalarType &kappa, - * const vnl_vector_fixed < ScalarType, DataDimension > &meanDirection, - * vnl_vector_fixed < ScalarType, DataDimension > &resVec, - * std::mt19937 &generator) - * - * \brief Sample from the Watson distribution using the procedure described in - * Fisher et al., Statistical Analysis of Spherical Data, 1993, p.59. - * - * \author Aymeric Stamm - * \date October 2013 - * - * \param kappa Concentration parameter of the Watson distribution. - * \param meanDirection Mean direction of the Watson distribution. - * \param resVec Resulting sample. - * \param generator Pseudo-random number generator. - **************************************************************************************************/ - - resVec.fill(0.0); - SampleFromWatsonDistribution(kappa, meanDirection, resVec, DataDimension, generator); -} - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const itk::Point < ScalarType, DataDimension > &meanDirection, itk::Point < ScalarType, DataDimension > &resVec, std::mt19937 &generator) -{ - /**********************************************************************************************//** - * \fn template - * void - * SampleFromWatsonDistribution(const ScalarType &kappa, - * const itk::Point < ScalarType, DataDimension > &meanDirection, - * itk::Point < ScalarType, DataDimension > &resVec, - * std::mt19937 &generator) - * - * \brief Sample from the Watson distribution using the procedure described in - * Fisher et al., Statistical Analysis of Spherical Data, 1993, p.59. - * - * \author Aymeric Stamm - * \date October 2013 - * - * \param kappa Concentration parameter of the Watson distribution. - * \param meanDirection Mean direction of the Watson distribution. - * \param resVec Resulting sample. - * \param generator Pseudo-random number generator. - **************************************************************************************************/ - - resVec.Fill(0.0); - SampleFromWatsonDistribution(kappa, meanDirection, resVec, DataDimension, generator); -} - -template -void -SampleFromWatsonDistribution(const ScalarType &kappa, const itk::Vector < ScalarType, DataDimension > &meanDirection, itk::Vector < ScalarType, DataDimension > &resVec, std::mt19937 &generator) -{ - /**********************************************************************************************//** - * \fn template - * void - * SampleFromWatsonDistribution(const ScalarType &kappa, - * const itk::Vector < ScalarType, DataDimension > &meanDirection, - * itk::Vector < ScalarType, DataDimension > &resVec, - * std::mt19937 &generator) - * - * \brief Sample from the Watson distribution using the procedure described in - * Fisher et al., Statistical Analysis of Spherical Data, 1993, p.59. - * - * \author Aymeric Stamm - * \date October 2013 - * - * \param kappa Concentration parameter of the Watson distribution. - * \param meanDirection Mean direction of the Watson distribution. - * \param resVec Resulting sample. - * \param generator Pseudo-random number generator. - **************************************************************************************************/ - - resVec.Fill(0.0); - SampleFromWatsonDistribution(kappa, meanDirection, resVec, DataDimension, generator); -} - -} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaGammaDistribution.cxx b/Anima/math-tools/statistical_distributions/animaGammaDistribution.cxx index cacaaeeeb..002497970 100644 --- a/Anima/math-tools/statistical_distributions/animaGammaDistribution.cxx +++ b/Anima/math-tools/statistical_distributions/animaGammaDistribution.cxx @@ -1,103 +1,167 @@ #include "animaGammaDistribution.h" -#include -#include +#include + #include +#include + namespace anima { -double GammaDistribution::GetDensity(const double &x) -{ - if (x < std::numeric_limits::epsilon()) - return 0.0; - return std::exp(this->GetLogDensity(x)); -} + bool GammaDistribution::BelongsToSupport(const ValueType &x) + { + return x > this->GetEpsilon(); + } -double GammaDistribution::GetLogDensity(const double &x) -{ - if (x < std::numeric_limits::epsilon()) - throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the Gamma distribution is not defined for negative or null arguments.", ITK_LOCATION); - double shapeParameter = this->GetShapeParameter(); - double scaleParameter = this->GetScaleParameter(); - double resValue = (shapeParameter - 1.0) * std::log(x); - resValue -= x / scaleParameter; - resValue -= std::lgamma(shapeParameter); - resValue -= shapeParameter * std::log(scaleParameter); - return resValue; -} - -void GammaDistribution::Fit(const VectorType &sample, const std::string &method) -{ - unsigned int dimValue = sample.size(); - double doubleDimValue = static_cast(dimValue); - double epsValue = std::numeric_limits::epsilon(); - double shapeParameter, scaleParameter; + void GammaDistribution::SetShapeParameter(const double val) + { + if (val < this->GetEpsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "The shape parameter of the Gamma distribution should be strictly positive.", ITK_LOCATION); + m_ShapeParameter = val; + } - if (method == "mle") + void GammaDistribution::SetScaleParameter(const double val) { - // Code for estimating theta and kappa via MLE - double meanValue = 0.0; - double meanLogValue = 0.0; - for (unsigned int i = 0;i < dimValue;++i) - { - double tmpValue = sample[i]; - if (tmpValue < std::numeric_limits::epsilon()) - throw itk::ExceptionObject(__FILE__, __LINE__, "Negative or null values are not allowed in a sample drawn from the Gamma distribution.", ITK_LOCATION); - meanValue += tmpValue; - meanLogValue += std::log(tmpValue); - } - meanValue /= doubleDimValue; - meanLogValue /= doubleDimValue; - double logMeanValue = std::log(meanValue); - - double sValue = logMeanValue - meanLogValue; - shapeParameter = (3.0 - sValue + std::sqrt((sValue - 3.0) * (sValue - 3.0) + 24.0 * sValue)) / (12.0 * sValue); - scaleParameter = 0.0; - if (shapeParameter > epsValue) - scaleParameter = meanValue / shapeParameter; + if (val < this->GetEpsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "The scale parameter of the Gamma distribution should be strictly positive.", ITK_LOCATION); + m_ScaleParameter = val; + } + + double GammaDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + return std::exp(this->GetLogDensity(x)); + } + + double GammaDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the Gamma distribution is not defined for negative or null arguments.", ITK_LOCATION); + ValueType resValue = (m_ShapeParameter - 1.0) * std::log(x); + resValue -= x / m_ScaleParameter; + resValue -= std::lgamma(m_ShapeParameter); + resValue -= m_ShapeParameter * std::log(m_ScaleParameter); + return resValue; } - else if (method == "biased-closed-form" || method == "unbiased-closed-form") + + double GammaDistribution::GetCumulative(const ValueType &x) { - // Code for estimating theta and kappa via CF biased and unbiased - double sumValue = 0.0; - double sumLogValue = 0.0; - double sumLogXValue = 0.0; - for (unsigned int i = 0;i < dimValue;++i) + /** + * \fn double GetCumulative(const double &x) + * + * \author Aymeric Stamm (2023). + * + * \param x A numeric value specifying a point on the support of the Gamma distribution. + * + * \return A numeric value storing the value of the cumulative distribution function of + * the Gamma distribution at point `x`. See: https://statproofbook.github.io/P/gam-cdf. + */ + + return boost::math::gamma_p(this->GetShapeParameter(), x / this->GetScaleParameter()); + } + + void GammaDistribution::Fit(const SampleType &sample, const std::string &method) + { + unsigned int dimValue = sample.size(); + double doubleDimValue = static_cast(dimValue); + double epsValue = this->GetEpsilon(); + double shapeParameter, scaleParameter; + + if (method == "mle") { - double tmpValue = sample[i]; - if (tmpValue < std::numeric_limits::epsilon()) - throw itk::ExceptionObject(__FILE__, __LINE__, "Negative or null values are not allowed in a sample drawn from the Gamma distribution.", ITK_LOCATION); - sumValue += tmpValue; - sumLogValue += std::log(tmpValue); - sumLogXValue += (std::log(tmpValue) * tmpValue); + // Code for estimating theta and kappa via MLE + double meanValue = 0.0; + double meanLogValue = 0.0; + for (unsigned int i = 0; i < dimValue; ++i) + { + double tmpValue = sample[i]; + if (tmpValue < epsValue) + throw itk::ExceptionObject(__FILE__, __LINE__, "Negative or null values are not allowed in a sample drawn from the Gamma distribution.", ITK_LOCATION); + meanValue += tmpValue; + meanLogValue += std::log(tmpValue); + } + meanValue /= doubleDimValue; + meanLogValue /= doubleDimValue; + double logMeanValue = std::log(meanValue); + + double sValue = logMeanValue - meanLogValue; + shapeParameter = (3.0 - sValue + std::sqrt((sValue - 3.0) * (sValue - 3.0) + 24.0 * sValue)) / (12.0 * sValue); + + if (shapeParameter < epsValue) + throw itk::ExceptionObject(__FILE__, __LINE__, "The shape parameter of a Gamma distribution cannot be negative or null.", ITK_LOCATION); + + scaleParameter = meanValue / shapeParameter; } + else if (method == "biased-closed-form" || method == "unbiased-closed-form") + { + // Code for estimating theta and kappa via CF biased and unbiased + double sumValue = 0.0; + double sumLogValue = 0.0; + double sumLogXValue = 0.0; + for (unsigned int i = 0; i < dimValue; ++i) + { + double tmpValue = sample[i]; + if (tmpValue < std::numeric_limits::epsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "Negative or null values are not allowed in a sample drawn from the Gamma distribution.", ITK_LOCATION); + sumValue += tmpValue; + sumLogValue += std::log(tmpValue); + sumLogXValue += (std::log(tmpValue) * tmpValue); + } - double denValue = doubleDimValue * sumLogXValue - sumLogValue * sumValue; - shapeParameter = doubleDimValue * sumValue; - scaleParameter = doubleDimValue * sumLogXValue - sumLogValue * sumValue; - if (denValue > epsValue) - shapeParameter /= denValue; + double denValue = doubleDimValue * sumLogXValue - sumLogValue * sumValue; + shapeParameter = doubleDimValue * sumValue; + scaleParameter = doubleDimValue * sumLogXValue - sumLogValue * sumValue; + if (denValue > epsValue) + shapeParameter /= denValue; scaleParameter /= (doubleDimValue * doubleDimValue); - - if (method == "unbiased-closed-form") - { - shapeParameter -= (3.0 * shapeParameter - 2.0 / 3.0 * shapeParameter / (1.0 + shapeParameter) - 4.0 / 5.0 * shapeParameter / ((1.0 + shapeParameter) * (1.0 + shapeParameter))) / doubleDimValue; - scaleParameter *= doubleDimValue / (doubleDimValue - 1.0); + + if (method == "unbiased-closed-form") + { + shapeParameter -= (3.0 * shapeParameter - 2.0 / 3.0 * shapeParameter / (1.0 + shapeParameter) - 4.0 / 5.0 * shapeParameter / ((1.0 + shapeParameter) * (1.0 + shapeParameter))) / doubleDimValue; + scaleParameter *= doubleDimValue / (doubleDimValue - 1.0); + } } + else + throw itk::ExceptionObject(__FILE__, __LINE__, "Unsupported estimation method for the Gamma distribution.", ITK_LOCATION); + + this->SetShapeParameter(shapeParameter); + this->SetScaleParameter(scaleParameter); } - else - throw itk::ExceptionObject(__FILE__, __LINE__, "Unsupported estimation method for the Gamma distribution.", ITK_LOCATION); - this->SetShapeParameter(shapeParameter); - this->SetScaleParameter(scaleParameter); -} + void GammaDistribution::Random(SampleType &sample, GeneratorType &generator) + { + DistributionType distributionValue(m_ShapeParameter, m_ScaleParameter); + unsigned int nSamples = sample.size(); + for (unsigned int i = 0; i < nSamples; ++i) + sample[i] = distributionValue(generator); + } + + double GammaDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double GetDistance(GammaDistribution *otherDistribution) + * + * \author Aymeric Stamm (2023). + * + * \param otherDistribution A pointer specifying another object of class `GammaDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence between the + * current Gamma distribution and the input Gamma distribution. The calculation is done as + * described in McCrimmon (2018), Distance metrics for Gamma distributions, arXiv:1802.01041v1. + * Also documented here: https://statproofbook.github.io/P/gam-kl. + */ + + double thisKappa = this->GetShapeParameter(); + double thisTheta = this->GetScaleParameter(); + + GammaDistribution *gammaDistr = dynamic_cast(otherDistribution); + double otherKappa = gammaDistr->GetShapeParameter(); + double otherTheta = gammaDistr->GetScaleParameter(); + + double distanceValue = (thisKappa - otherKappa) * (anima::digamma(thisKappa) + std::log(thisTheta) - anima::digamma(otherKappa) - std::log(otherTheta)); + distanceValue += (thisKappa * thisTheta - otherKappa * otherTheta) * (thisTheta - otherTheta) / (thisTheta * otherTheta); + return distanceValue; + } -void GammaDistribution::Random(VectorType &sample, GeneratorType &generator) -{ - DistributionType distributionValue(this->GetShapeParameter(), this->GetScaleParameter()); - unsigned int nSamples = sample.size(); - for (unsigned int i = 0;i < nSamples;++i) - sample[i] = distributionValue(generator); -} - } // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaGammaDistribution.h b/Anima/math-tools/statistical_distributions/animaGammaDistribution.h index 516a65cb2..d5964ae08 100644 --- a/Anima/math-tools/statistical_distributions/animaGammaDistribution.h +++ b/Anima/math-tools/statistical_distributions/animaGammaDistribution.h @@ -4,14 +4,36 @@ namespace anima { - class ANIMASTATISTICALDISTRIBUTIONS_EXPORT GammaDistribution : public BaseDistribution + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT GammaDistribution : public BaseDistribution { public: - using DistributionType = std::gamma_distribution<>; - double GetDensity(const double &x); - double GetLogDensity(const double &x); - void Fit(const VectorType &sample, const std::string &method); - void Random(VectorType &sample, GeneratorType &generator); + using DistributionType = std::gamma_distribution; + + GammaDistribution() + { + m_ShapeParameter = 1.0; + m_ScaleParameter = 1.0; + } + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean() { return m_ShapeParameter * m_ScaleParameter; } + double GetVariance() { return m_ShapeParameter * m_ScaleParameter * m_ScaleParameter; } + double GetDistance(Self *otherDistribution); + + void SetShapeParameter(const double val); + double GetShapeParameter() { return m_ShapeParameter; } + + void SetScaleParameter(const double val); + double GetScaleParameter() { return m_ScaleParameter; } + + private: + double m_ShapeParameter; + double m_ScaleParameter; }; - + } // end of namespace diff --git a/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.cxx b/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.cxx new file mode 100644 index 000000000..f98336c6b --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.cxx @@ -0,0 +1,222 @@ +#include "animaMultivariateNormalDistribution.h" +#include + +#include + +#include + +namespace anima +{ + void MultivariateNormalDistribution::SetCovarianceMatrixParameter(const MatrixType &val) + { + unsigned int dimValue = val.rows(); + + if (val.cols() != dimValue) + throw itk::ExceptionObject(__FILE__, __LINE__, "The covariance matrix is not square.", ITK_LOCATION); + + bool isSymmetric = true; + for (unsigned int i = 0; i < dimValue; ++i) + { + for (unsigned int j = i + 1; j < dimValue; ++j) + { + if (val.get(i, j) != val.get(j, i)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The covariance matrix is not symmetric.", ITK_LOCATION); + } + } + + m_CovarianceMatrixDeterminant = vnl_determinant(val); + if (m_CovarianceMatrixDeterminant < this->GetEpsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "The covariance matrix is not definite positive.", ITK_LOCATION); + + m_CovarianceMatrixParameter = val; + + MatrixType eigenVecs(dimValue, dimValue); + vnl_diag_matrix eigenVals(dimValue), sqrtEigenVals(dimValue), invEigenVals(dimValue); + + itk::SymmetricEigenAnalysis, MatrixType> eigenComputer(dimValue); + eigenComputer.ComputeEigenValuesAndVectors(m_CovarianceMatrixParameter, eigenVals, eigenVecs); + + for (unsigned int i = 0; i < dimValue; ++i) + { + sqrtEigenVals[i] = std::sqrt(eigenVals[i]); + invEigenVals[i] = 1.0 / eigenVals[i]; + } + + anima::RecomposeTensor(sqrtEigenVals, eigenVecs, m_SqrtCovarianceMatrixParameter); + anima::RecomposeTensor(invEigenVals, eigenVecs, m_PrecisionMatrixParameter); + } + + double MultivariateNormalDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + + return std::exp(this->GetLogDensity(x)); + } + + double MultivariateNormalDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density is not defined outside the support.", ITK_LOCATION); + + unsigned int numParameters = x.size(); + + if (m_MeanParameter.size() != numParameters || m_CovarianceMatrixParameter.rows() != numParameters) + throw itk::ExceptionObject(__FILE__, __LINE__, "The input argument does not belong to the same vector space as the one on which the normal distribution is defined.", ITK_LOCATION); + + double resValue = -static_cast(numParameters) / 2.0 * std::log(2.0 * M_PI); + resValue -= 0.5 * std::log(m_CovarianceMatrixDeterminant); + + double tmpValue = 0.0; + for (unsigned int i = 0; i < numParameters; ++i) + for (unsigned int j = 0; j < numParameters; ++j) + tmpValue += m_PrecisionMatrixParameter.get(i, j) * (x[i] - m_MeanParameter[i]) * (x[j] * m_MeanParameter[j]); + + resValue -= 0.5 * tmpValue; + + return resValue; + } + + double MultivariateNormalDistribution::GetCumulative(const ValueType &x) + { + /** + * \fn double MultivariateNormalDistribution::GetCumulative(const std::vector &x) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param x A numeric vector specifying a point in R^p. + * + * \return A numeric value storing the value of the CDF of the multivariate normal distribution + * at point `x`. There is no closed-form expression of this function. Hence it is evaluated + * numerically via Monte-Carlo approximation. + */ + + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF is not defined outside the support.", ITK_LOCATION); + + unsigned int numParameters = x.size(); + + if (m_MeanParameter.size() != numParameters) + throw itk::ExceptionObject(__FILE__, __LINE__, "The input argument does not belong to the support as the one on which the normal distribution is defined.", ITK_LOCATION); + + const unsigned int numberOfMonteCarloSamples = 10000; + SampleType normalSample(numberOfMonteCarloSamples); + GeneratorType generator; + this->Random(normalSample, generator); + + double sumValue = 0.0; + for (unsigned int i = 0; i < numberOfMonteCarloSamples; ++i) + { + double sampleValue = 1.0; + for (unsigned int j = 0; j < numParameters; ++j) + { + if (normalSample[i][j] > x[j]) + { + sampleValue = 0.0; + break; + } + } + + sumValue += sampleValue; + } + + return sumValue / static_cast(numberOfMonteCarloSamples); + } + + void MultivariateNormalDistribution::Fit(const SampleType &sample, const std::string &method) + { + unsigned int numObservations = sample.size(); + unsigned int numParameters = m_MeanParameter.size(); + + ValueType meanValue(numParameters); + for (unsigned int j = 0; j < numParameters; ++j) + { + meanValue[j] = 0.0; + for (unsigned int i = 0; i < numObservations; ++i) + meanValue[j] += sample[i][j]; + meanValue[j] /= static_cast(numObservations); + } + + this->SetMeanParameter(meanValue); + + MatrixType covarianceMatrix(numParameters, numParameters); + for (unsigned int j = 0; j < numParameters; ++j) + { + for (unsigned int k = 0; k < numParameters; ++k) + { + double matrixEntry = 0.0; + for (unsigned int i = 0; i < numObservations; ++i) + matrixEntry += (sample[i][j] - meanValue[j]) * (sample[i][k] - meanValue[k]); + matrixEntry /= static_cast(numObservations); + + covarianceMatrix.put(j, k, matrixEntry); + if (j != k) + covarianceMatrix.put(k, j, matrixEntry); + } + } + + this->SetCovarianceMatrixParameter(covarianceMatrix); + } + + void MultivariateNormalDistribution::Random(SampleType &sample, GeneratorType &generator) + { + NormalDistributionType normDistr(0.0, 1.0); + unsigned int numObservations = sample.size(); + unsigned int numParameters = m_MeanParameter.size(); + ValueType tmpValue(numParameters); + + for (unsigned int i = 0; i < numObservations; ++i) + { + for (unsigned int j = 0; j < numParameters; ++j) + tmpValue[j] = normDistr(generator); + + sample[i].resize(numParameters); + for (unsigned int j = 0; j < numParameters; ++j) + { + sample[i][j] = m_MeanParameter[j]; + for (unsigned int k = 0; k < numParameters; ++k) + sample[i][j] += m_SqrtCovarianceMatrixParameter.get(j, k) * tmpValue[k]; + } + } + } + + double MultivariateNormalDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double MultivariateNormalDistribution::GetDistance(MultivariateNormalDistribution *otherDistribution) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param otherDistribution A pointer specifying another object of class + * `MultivariateNormalDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence to the input normal + * distribution. The implementation follows the formula in https://statproofbook.github.io/P/mvn-kl. + */ + + MultivariateNormalDistribution *normDistr = dynamic_cast(otherDistribution); + ValueType otherMeanParam = normDistr->GetMeanParameter(); + MatrixType otherCovParam = normDistr->GetCovarianceMatrixParameter(); + MatrixType otherPrecParam = normDistr->GetPrecisionMatrixParameter(); + + unsigned int numParams = m_MeanParameter.size(); + if (otherMeanParam.size() != numParams) + throw itk::ExceptionObject(__FILE__, __LINE__, "The two compared distributions should be on the same support.", ITK_LOCATION); + + double quadForm = 0.0; + double firstTraceValue = 0.0, secondTraceValue = 0.0; + for (unsigned int j = 0; j < numParams; ++j) + { + for (unsigned int k = 0; k < numParams; ++k) + { + quadForm += (otherMeanParam[j] - m_MeanParameter[j]) * (otherMeanParam[k] - m_MeanParameter[k]) * (m_PrecisionMatrixParameter.get(j, k) + otherPrecParam.get(j, k)); + firstTraceValue += otherPrecParam.get(j, k) * m_CovarianceMatrixParameter.get(k, j); + secondTraceValue += m_PrecisionMatrixParameter.get(j, k) * otherCovParam.get(k, j); + } + } + + return quadForm / 2.0 + firstTraceValue + secondTraceValue - static_cast(numParams); + } +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.h b/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.h new file mode 100644 index 000000000..583960ad7 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaMultivariateNormalDistribution.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include + +namespace anima +{ + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT MultivariateNormalDistribution : public BaseDistribution> + { + public: + using NormalDistributionType = std::normal_distribution; + using MatrixType = vnl_matrix; + + MultivariateNormalDistribution() + { + m_MeanParameter.clear(); + m_CovarianceMatrixParameter.clear(); + m_SqrtCovarianceMatrixParameter.clear(); + m_PrecisionMatrixParameter.clear(); + m_CovarianceMatrixDeterminant = 1.0; + } + + bool BelongsToSupport(const ValueType &x) { return true; } + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean() { return m_MeanParameter; } + double GetVariance() { return vnl_trace(this->GetCovarianceMatrixParameter()); } + double GetDistance(Self *otherDistribution); + + void SetMeanParameter(const ValueType &val) { m_MeanParameter = val; } + ValueType GetMeanParameter() { return m_MeanParameter; } + + void SetCovarianceMatrixParameter(const MatrixType &val); + MatrixType GetCovarianceMatrixParameter() { return m_CovarianceMatrixParameter; } + + MatrixType GetPrecisionMatrixParameter() { return m_PrecisionMatrixParameter; } + + private: + ValueType m_MeanParameter; + MatrixType m_CovarianceMatrixParameter, m_SqrtCovarianceMatrixParameter, m_PrecisionMatrixParameter; + double m_CovarianceMatrixDeterminant; + }; + +} // end of namespace diff --git a/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.cxx b/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.cxx new file mode 100644 index 000000000..f62028e04 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.cxx @@ -0,0 +1,85 @@ +#include "animaRealUniformDistribution.h" + +#include + +#include + +namespace anima +{ + + bool RealUniformDistribution::BelongsToSupport(const ValueType &x) + { + return (x >= m_LowerBoundParameter) && (x <= m_UpperBoundParameter); + } + + double RealUniformDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + return std::exp(this->GetLogDensity(x)); + } + + double RealUniformDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the uniform distribution is not defined outside its support.", ITK_LOCATION); + + return -std::log(m_UpperBoundParameter - m_LowerBoundParameter); + } + + double RealUniformDistribution::GetCumulative(const ValueType &x) + { + /** + * \fn double RealUniformDistribution::GetCumulative(const double &x) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param x A numeric value specifying a point on the support of the real uniform + * distribution. + * + * \return A numeric value storing the value of the cumulative distribution function + * of the real uniform distribution at point `x`. + */ + + if (x < m_LowerBoundParameter) + return 0.0; + + if (x > m_UpperBoundParameter) + return 1.0; + + return (x - m_LowerBoundParameter) / (m_UpperBoundParameter - m_LowerBoundParameter); + } + + void RealUniformDistribution::Fit(const SampleType &sample, const std::string &method) + { + this->SetLowerBoundParameter(*std::min_element(sample.begin(), sample.end())); + this->SetUpperBoundParameter(*std::max_element(sample.begin(), sample.end())); + } + + void RealUniformDistribution::Random(SampleType &sample, GeneratorType &generator) + { + UniformDistributionType unifDistr(m_LowerBoundParameter, m_UpperBoundParameter); + unsigned int nSamples = sample.size(); + for (unsigned int i = 0; i < nSamples; ++i) + sample[i] = unifDistr(generator); + } + + double RealUniformDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double RealUniformDistribution::GetDistance(Self *otherDistribution) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param otherDistribution A pointer specifying another object of class `RealUniformDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence with the + * input real uniform distribution. See: https://statproofbook.github.io/P/cuni-kl. + */ + + return 0.0; + } + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.h b/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.h new file mode 100644 index 000000000..23d98386d --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaRealUniformDistribution.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace anima +{ + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT RealUniformDistribution : public BaseDistribution + { + public: + using UniformDistributionType = std::uniform_real_distribution; + + RealUniformDistribution() + { + m_LowerBoundParameter = 0.0; + m_UpperBoundParameter = 1.0; + } + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean() { return (m_LowerBoundParameter + m_UpperBoundParameter) / 2.0; } + double GetVariance() { return (m_UpperBoundParameter - m_LowerBoundParameter) * (m_UpperBoundParameter - m_LowerBoundParameter) / 12.0; } + double GetDistance(Self *otherDistribution); + + void SetLowerBoundParameter(const double val) { m_LowerBoundParameter = val; } + double GetLowerBoundParameter() { return m_LowerBoundParameter; } + + void SetUpperBoundParameter(const double val) { m_UpperBoundParameter = val; } + double GetUpperBoundParameter() { return m_UpperBoundParameter; } + + private: + double m_LowerBoundParameter; + double m_UpperBoundParameter; + }; + +} // end of namespace diff --git a/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h b/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h deleted file mode 100644 index 50bccb90f..000000000 --- a/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include -#include - -namespace anima -{ - -template -class SampleImageFromDistributionImageFilter : - public itk::ImageToImageFilter< itk::VectorImage , itk::VectorImage > -{ -public: - /** Standard class typedefs. */ - typedef SampleImageFromDistributionImageFilter Self; - typedef itk::VectorImage TInputImage; - typedef itk::VectorImage TOutputImage; - typedef itk::ImageToImageFilter< TInputImage, TOutputImage > Superclass; - typedef itk::SmartPointer Pointer; - typedef itk::SmartPointer ConstPointer; - - /** Method for creation through the object factory. */ - itkNewMacro(Self) - - /** Run-time type information (and related methods) */ - itkTypeMacro(SampleImageFromDistributionImageFilter, ImageToImageFilter) - - typedef typename TInputImage::Pointer InputImagePointer; - typedef typename TInputImage::PixelType InputImagePixel; - typedef typename TOutputImage::Pointer OutputImagePointer; - - /** Superclass typedefs. */ - typedef typename Superclass::OutputImageRegionType OutputImageRegionType; - -protected: - SampleImageFromDistributionImageFilter() - { - m_VectorSize = 3; - } - - virtual ~SampleImageFromDistributionImageFilter() {} - - void GenerateOutputInformation() ITK_OVERRIDE; - void BeforeThreadedGenerateData() ITK_OVERRIDE; - void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; - -private: - ITK_DISALLOW_COPY_AND_ASSIGN(SampleImageFromDistributionImageFilter); - - InputImagePixel m_BaseDistributionSample; - unsigned int m_VectorSize; -}; - -} // end namespace anima - -#include "animaSampleImageFromDistributionImageFilter.hxx" diff --git a/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.hxx b/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.hxx deleted file mode 100644 index 0b8d3fb98..000000000 --- a/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.hxx +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace anima -{ - -template -void -SampleImageFromDistributionImageFilter -::GenerateOutputInformation() -{ - // Override the method in itkImageSource, so we can set the vector length of - // the output itk::VectorImage - - this->Superclass::GenerateOutputInformation(); - - m_VectorSize = this->GetInput(0)->GetNumberOfComponentsPerPixel(); - TOutputImage *output = this->GetOutput(); - output->SetVectorLength(m_VectorSize); -} - -template -void -SampleImageFromDistributionImageFilter -::BeforeThreadedGenerateData () -{ - if (this->GetNumberOfIndexedInputs() != 2) - itkExceptionMacro("Two inputs required: average and covariance images"); - - std::mt19937 motherGenerator(time(0)); - InputImagePixel mean(m_VectorSize); - mean.Fill(0); - vnl_matrix cov(m_VectorSize,m_VectorSize); - cov.set_identity(); - - m_BaseDistributionSample = InputImagePixel(m_VectorSize); - //FIXME oops :( - anima::SampleFromMultivariateGaussianDistribution(mean,cov,m_BaseDistributionSample,motherGenerator); -} - -template -void -SampleImageFromDistributionImageFilter -::DynamicThreadedGenerateData (const OutputImageRegionType &outputRegionForThread) -{ - typedef itk::ImageRegionConstIterator InputImageConstIteratorType; - typedef itk::ImageRegionIterator InputImageIteratorType; - - InputImageConstIteratorType meanItr(this->GetInput(0),outputRegionForThread); - InputImageConstIteratorType covItr(this->GetInput(1),outputRegionForThread); - InputImageIteratorType outItr(this->GetOutput(),outputRegionForThread); - - InputImagePixel sample(m_VectorSize), covLine, meanLine; - vnl_matrix covMat(m_VectorSize,m_VectorSize); - vnl_matrix eVecs(m_VectorSize,m_VectorSize); - vnl_diag_matrix eVals(m_VectorSize); - - itk::SymmetricEigenAnalysis < vnl_matrix , vnl_diag_matrix, vnl_matrix > eigenComputer(m_VectorSize); - - while (!outItr.IsAtEnd()) - { - covLine = covItr.Get(); - meanLine = meanItr.Get(); - sample.Fill(0); - - unsigned int pos = 0; - bool nullMat = true; - for (unsigned int i = 0;i < m_VectorSize;++i) - for (unsigned int j = i;j < m_VectorSize;++j) - { - if (covLine[pos] != 0) - nullMat = false; - covMat(i,j) = covLine[pos]; - if (i != j) - covMat(j,i) = covMat(i,j); - - ++pos; - } - - if (nullMat) - { - outItr.Set(sample); - ++meanItr; - ++covItr; - ++outItr; - - continue; - } - - eigenComputer.ComputeEigenValuesAndVectors(covMat, eVals, eVecs); - - for (unsigned int i = 0;i < m_VectorSize;++i) - eVals[i] = sqrt(eVals[i]); - - anima::RecomposeTensor(eVals,eVecs,covMat); - - sample = m_BaseDistributionSample; - for (unsigned int i = 0;i < m_VectorSize;++i) - { - sample[i] = 0; - for (unsigned int j = 0;j < m_VectorSize;++j) - sample[i] += covMat(i,j) * m_BaseDistributionSample[j]; - - sample[i] += meanLine[i]; - } - - outItr.Set(sample); - - ++meanItr; - ++covItr; - ++outItr; - } -} - -} // end namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.cxx b/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.cxx new file mode 100644 index 000000000..5d9e6fae4 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.cxx @@ -0,0 +1,88 @@ +#include "animaSphericalUniformDistribution.h" +#include + +#include + +namespace anima +{ + + bool SphericalUniformDistribution::BelongsToSupport(const ValueType &x) + { + return std::abs(x.GetNorm() - 1.0) < this->GetEpsilon(); + } + + double SphericalUniformDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + return std::exp(this->GetLogDensity(x)); + } + + double SphericalUniformDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density is not defined outside the support.", ITK_LOCATION); + + return -std::log(4.0 * M_PI); + } + + double SphericalUniformDistribution::GetCumulative(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF is not defined outside the support.", ITK_LOCATION); + + ValueType sphCoords; + anima::TransformCartesianToSphericalCoordinates(x, sphCoords); + double thetaVal = sphCoords[0]; + while (thetaVal > M_PI) + thetaVal -= (2.0 * M_PI); + while (thetaVal < 0) + thetaVal += (2.0 * M_PI); + double phiVal = sphCoords[1]; + while (phiVal > 2.0 * M_PI) + phiVal -= (2.0 * M_PI); + while (phiVal < 0) + phiVal += 2.0 * M_PI; + + return phiVal / (2.0 * M_PI) * (1.0 - std::cos(thetaVal)) / 2.0; + } + + void SphericalUniformDistribution::Random(SampleType &sample, GeneratorType &generator) + { + UniformDistributionType unifDistr(0.0, 1.0); + ValueType sphCoords; + sphCoords[2] = 1.0; + unsigned int nSamples = sample.size(); + for (unsigned int i = 0; i < nSamples; ++i) + { + sphCoords[0] = 2.0 * std::asin(std::sqrt(unifDistr(generator))); + sphCoords[1] = 2.0 * M_PI * unifDistr(generator); + anima::TransformSphericalToCartesianCoordinates(sphCoords, sample[i]); + } + } + + SphericalUniformDistribution::ValueType SphericalUniformDistribution::GetMean() + { + ValueType meanValue; + meanValue.Fill(0.0); + return meanValue; + } + + double SphericalUniformDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double SphericalUniformDistribution::GetDistance(Self *otherDistribution) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param otherDistribution A pointer specifying another object of class `SphericalUniformDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence with the + * input spherical uniform distribution. + */ + + return 0.0; + } + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.h b/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.h new file mode 100644 index 000000000..947a517c5 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaSphericalUniformDistribution.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include + +namespace anima +{ + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT SphericalUniformDistribution : public BaseDistribution> + { + public: + using UniformDistributionType = std::uniform_real_distribution; + + SphericalUniformDistribution() {} + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method) { return; } + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean(); + double GetVariance() { return 0.0; } + double GetDistance(Self *otherDistribution); + }; + +} // end of namespace diff --git a/Anima/math-tools/statistical_distributions/animaVMFDistribution.h b/Anima/math-tools/statistical_distributions/animaVMFDistribution.h deleted file mode 100644 index 8f40fd624..000000000 --- a/Anima/math-tools/statistical_distributions/animaVMFDistribution.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -namespace anima -{ - -template -double ComputeVMFPdf(const VectorType &v, const VectorType &meanDirection, const ScalarType &kappa); - -template -double VMFDistance(const itk::Point &muFirst, const double &kappaFirst, - const itk::Point &muSec, const double &kappaSec); - -//! Maximum likelihood estimation of the concentration parameter of the 2D von Mises distribution according to Mardia, Statistics of Directional Data, 1972. -template -double GetVonMisesConcentrationMLE(const ScalarType rbar); - -} // end of namespace - -#include "animaVMFDistribution.hxx" diff --git a/Anima/math-tools/statistical_distributions/animaVMFDistribution.hxx b/Anima/math-tools/statistical_distributions/animaVMFDistribution.hxx deleted file mode 100644 index 8cf3f9407..000000000 --- a/Anima/math-tools/statistical_distributions/animaVMFDistribution.hxx +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once -#include - -#include "animaVMFDistribution.h" -#include -#include - -#include - -#include - -namespace anima -{ - -template double ComputeVMFPdf(const VectorType &v, const VectorType &meanDirection, const ScalarType &kappa) -{ - if (std::abs(anima::ComputeNorm(meanDirection) - 1.0) > 1.0e-6 || std::abs(anima::ComputeNorm(v) - 1.0) > 1.0e-6) - { - std::cout << "Norm of mean direction: " << anima::ComputeNorm(meanDirection) << std::endl; - std::cout << "Norm of evaluated direction: " << anima::ComputeNorm(v) << std::endl; - throw itk::ExceptionObject(__FILE__, __LINE__,"Von Mises & Fisher distribution requires unitary vectors.",ITK_LOCATION); - } - - double resVal; - - if (kappa < 1e-4) - { - resVal = std::exp(kappa * anima::ComputeScalarProduct(meanDirection, v)) / (4.0 * M_PI); - } - else - { - double tmpVal = anima::ComputeScalarProduct(meanDirection, v) - 1.0; - tmpVal *= kappa; - - resVal = std::exp(tmpVal); - resVal *= kappa; - tmpVal = 1.0 - std::exp(-2.0 * kappa); - resVal /= (2.0 * M_PI * tmpVal); - } - - return resVal; -} - -template -double -VMFDistance(const itk::Point &muFirst, const double &kappaFirst, - const itk::Point &muSec, const double &kappaSec) -{ - double dist = 0; - - double scalarProduct = 0; - for (unsigned int i = 0;i < Dimension;++i) - scalarProduct += muFirst[i] * muSec[i]; - - if (scalarProduct < -1.0) - scalarProduct = -1.0; - - if (scalarProduct > 1.0) - scalarProduct = 1.0; - - double acosValue = std::acos(scalarProduct); - - dist = anima::safe_log(std::max(1.0e-16,kappaSec) / std::max(1.0e-16,kappaFirst)); - dist *= dist; - dist += acosValue * acosValue; - - return sqrt(dist); -} - -template -double -GetVonMisesConcentrationMLE(const ScalarType rbar) -{ - // This function performs maximum-likelihood estimation of the concentration parameter for the 2D von Mises distribution. Given \theta_1, ..., \theta_n n i.i.d. angles following the same von Mises distribution with mean \mu and concentration parameter \kappa, rbar is expected to be the square root of the sum of the squared average cosine of the angles and the squared average sine of the angles. - - double kappa = 0.0; - - if (rbar < 0.44639) - { - // Approximation given by Eq. (5.4.8) p. 123. Provides two figure accuracy for rbar < 0.45 - double rbarSq = rbar * rbar; - kappa = rbar * (12.0 + 6.0 * rbarSq + 5.0 * rbarSq * rbarSq) / 6.0; - } // Next, linear interpolation between tabulated values reported in Appendix 2.2 p. 297 - else if (rbar < 0.48070) - kappa = 0.1 * (rbar - 0.44639) / (0.48070 - 0.44639) + 1.0; - else if (rbar < 0.51278) - kappa = 0.1 * (rbar - 0.48070) / (0.51278 - 0.48070) + 1.1; - else if (rbar < 0.54267) - kappa = 0.1 * (rbar - 0.51278) / (0.54267 - 0.51278) + 1.2; - else if (rbar < 0.57042) - kappa = 0.1 * (rbar - 0.54267) / (0.57042 - 0.54267) + 1.3; - else if (rbar < 0.59613) - kappa = 0.1 * (rbar - 0.57042) / (0.59613 - 0.57042) + 1.4; - else if (rbar < 0.61990) - kappa = 0.1 * (rbar - 0.59613) / (0.61990 - 0.59613) + 1.5; - else if (rbar < 0.64183) - kappa = 0.1 * (rbar - 0.61990) / (0.64183 - 0.61990) + 1.6; - else if (rbar < 0.66204) - kappa = 0.1 * (rbar - 0.64183) / (0.66204 - 0.64183) + 1.7; - else if (rbar < 0.68065) - kappa = 0.1 * (rbar - 0.66204) / (0.68065 - 0.66204) + 1.8; - else if (rbar < 0.69777) - kappa = 0.1 * (rbar - 0.68065) / (0.69777 - 0.68065) + 1.9; - else if (rbar < 0.71353) - kappa = 0.1 * (rbar - 0.69777) / (0.71353 - 0.69777) + 2.0; - else if (rbar < 0.72803) - kappa = 0.1 * (rbar - 0.71353) / (0.72803 - 0.71353) + 2.1; - else if (rbar < 0.74138) - kappa = 0.1 * (rbar - 0.72803) / (0.74138 - 0.72803) + 2.2; - else if (rbar < 0.75367) - kappa = 0.1 * (rbar - 0.74138) / (0.75367 - 0.74138) + 2.3; - else if (rbar < 0.76500) - kappa = 0.1 * (rbar - 0.75367) / (0.76500 - 0.75367) + 2.4; - else if (rbar < 0.77545) - kappa = 0.1 * (rbar - 0.76500) / (0.77545 - 0.76500) + 2.5; - else if (rbar < 0.78511) - kappa = 0.1 * (rbar - 0.77545) / (0.78511 - 0.77545) + 2.6; - else if (rbar < 0.79404) - kappa = 0.1 * (rbar - 0.78511) / (0.79404 - 0.78511) + 2.7; - else if (rbar < 0.80231) - kappa = 0.1 * (rbar - 0.79404) / (0.80231 - 0.79404) + 2.8; - else - { - // Approximation given by Eq. (5.4.10) p. 123. Provides three figure accuracy for rbar > 0.8 - double irbar = 1.0 - rbar; - double irbarSq = irbar * irbar; - kappa = 1.0 / (2.0 * irbar - irbarSq - irbar * irbarSq); - } - - return kappa; -} - -} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.cxx b/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.cxx new file mode 100644 index 000000000..867fc8c4b --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.cxx @@ -0,0 +1,382 @@ +#include "animaVonMisesFisherDistribution.h" +#include +#include +#include + +#include + +namespace anima +{ + + bool VonMisesFisherDistribution::BelongsToSupport(const ValueType &x) + { + return std::abs(x.GetNorm() - 1.0) < this->GetEpsilon(); + } + + void VonMisesFisherDistribution::SetMeanDirection(const ValueType &val) + { + if (!this->BelongsToSupport(val)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The mean direction parameter of the von Mises Fisher distribution should be of unit norm.", ITK_LOCATION); + m_MeanDirection = val; + } + + void VonMisesFisherDistribution::SetConcentrationParameter(const double &val) + { + if (val < this->GetEpsilon()) + throw itk::ExceptionObject(__FILE__, __LINE__, "The concentration parameter of the von Mises Fisher distribution should be positive.", ITK_LOCATION); + m_ConcentrationParameter = val; + m_BesselRatio = anima::bessel_ratio_i_lower_bound(val, static_cast(m_AmbientDimension) / 2.0); + } + + double VonMisesFisherDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + + double kappa = this->GetConcentrationParameter(); + ValueType meanDirection = this->GetMeanDirection(); + + if (kappa < std::sqrt(this->GetEpsilon())) + return std::exp(kappa * anima::ComputeScalarProduct(meanDirection, x)) / (4.0 * M_PI); + + double tmpVal = kappa * (anima::ComputeScalarProduct(meanDirection, x) - 1.0); + double resVal = std::exp(tmpVal); + resVal *= kappa; + tmpVal = 1.0 - std::exp(-2.0 * kappa); + resVal /= (2.0 * M_PI * tmpVal); + + return resVal; + } + + double VonMisesFisherDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the von Mises Fisher distribution is not defined for arguments outside the 2-sphere.", ITK_LOCATION); + + return std::log(this->GetDensity(x)); + } + + double VonMisesFisherDistribution::GetCumulative(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF is not defined outside the support.", ITK_LOCATION); + + ValueType sphCoords; + anima::TransformCartesianToSphericalCoordinates(x, sphCoords); + double thetaVal = sphCoords[0]; + while (thetaVal > M_PI) + thetaVal -= (2.0 * M_PI); + while (thetaVal < 0) + thetaVal += (2.0 * M_PI); + double phiVal = sphCoords[1]; + while (phiVal > 2.0 * M_PI) + phiVal -= (2.0 * M_PI); + while (phiVal < 0) + phiVal += 2.0 * M_PI; + + double phiCumul = phiVal / (2.0 * M_PI); + double thetaCumul = (1.0 - std::exp(-m_ConcentrationParameter * (1.0 - std::cos(thetaVal)))); + thetaCumul /= (1.0 - std::exp(-2.0 * m_ConcentrationParameter)); + + return phiCumul * thetaCumul; + } + + void VonMisesFisherDistribution::Fit(const SampleType &sample, const std::string &method) + { + /********************************************************************************************** + * \fn void VonMisesFisherDistribution::Fit(std::vector>, + * std::mt19937 &generator) + * + * \brief Closed-form approximations of the maximum likelihood estimators for the mean + * direction and concentration parameter of the von Mises Fisher distribution using + * the procedure described in Sra, A short note on parameter approximation for von + * Mises-Fisher distributions: and a fast implementation of Is(x), Computational + * Statistics, 2011. + * + * \author Aymeric Stamm + * \date October 2023 + * + * \param sample A numeric matrix of shape n x 3 specifying a sample of size n drawn from the + * von Mises Fisher distribution on the 2-sphere. + * \param method A string specifying the estimation method. Unused here. + **********************************************************************************************/ + + unsigned int numberOfObservations = sample.size(); + double ambientDimension = static_cast(m_AmbientDimension); + + // Eq. (2) + ValueType meanDirection; + meanDirection.Fill(0.0); + for (unsigned int i = 0; i < numberOfObservations; ++i) + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + meanDirection[j] += sample[i][j]; + double normValue = meanDirection.Normalize(); + double resultantValue = normValue / static_cast(numberOfObservations); + + if (std::abs(resultantValue - 1.0) < this->GetEpsilon()) + { + // All observations are perfectly aligned + // Kappa should be close to 0. + this->SetMeanDirection(meanDirection); + this->SetConcentrationParameter(this->GetEpsilon()); + return; + } + + // Eq. (4) + double concentrationParameter = resultantValue * (ambientDimension - resultantValue * resultantValue); + concentrationParameter /= (1.0 - resultantValue * resultantValue); + + // Eq. (6) + bool continueLoop = true; + while (continueLoop) + { + double oldConcentrationParameter = concentrationParameter; + double besselRatio = anima::bessel_ratio_i_lower_bound(concentrationParameter, ambientDimension / 2.0); + double tmpValue = besselRatio - resultantValue; + tmpValue /= (1.0 - besselRatio * besselRatio - (ambientDimension - 1.0) / concentrationParameter * besselRatio); + concentrationParameter -= tmpValue; + if (std::abs(concentrationParameter - oldConcentrationParameter) < this->GetEpsilon()) + continueLoop = false; + } + + this->SetMeanDirection(meanDirection); + this->SetConcentrationParameter(concentrationParameter); + } + + void VonMisesFisherDistribution::Random(SampleType &sample, GeneratorType &generator) + { + /********************************************************************************************** + * \fn void Random(std::vector>, std::mt19937 &generator) + * + * \brief Sample from the Watson distribution using the procedure described in Fisher et al., + * Statistical Analysis of Spherical Data, Cambridge University Press, 1993, pp. 59. + * + * \author Aymeric Stamm + * \date October 2013 + * + * \param sample A numeric matrix of shape n x 3 storing a sample of size n drawn from the + * Watson distribution on the 2-sphere. + * \param generator A pseudo-random number generator. + **********************************************************************************************/ + + unsigned int sampleSize = sample.size(); + ValueType sampleValue; + + if (m_ConcentrationParameter > 700.0) + { + for (unsigned int i = 0; i < sampleSize; ++i) + { + this->SampleFromVMFDistributionNumericallyStable(sampleValue, generator); + sample[i] = sampleValue; + } + } + else + { + for (unsigned int i = 0; i < sampleSize; ++i) + { + this->SampleFromVMFDistribution(sampleValue, generator); + sample[i] = sampleValue; + } + } + } + + double VonMisesFisherDistribution::GetDistance(Self *otherDistribution) + { + /** + * \fn double VonMisesFisherDistribution::GetDistance(VonMisesFisherDistribution *otherDistribution) + * + * \author Aymeric Stamm + * \date November 2023 + * + * \param otherDistribution An object of class `VonMisesFisherDistribution`. + * + * \return A numeric value storing the symmetric Kullback-Leibler divergence to the input von Mises + * Fisher distribution. This is achieved following Kitagawa & Rowley (2022), von Mises-Fisher + * distributions and their statistical divergence, arXiv:2202.05192v1 + * (https://arxiv.org/pdf/2202.05192v1.pdf). + */ + + VonMisesFisherDistribution *vmfDistr = dynamic_cast(otherDistribution); + ValueType otherMeanDirection = vmfDistr->GetMeanDirection(); + + if (otherMeanDirection.GetVectorDimension() != m_AmbientDimension) + throw itk::ExceptionObject(__FILE__, __LINE__, "The two compared distributions should be on the same sphere.", ITK_LOCATION); + + double otherConcentrationParameter = vmfDistr->GetConcentrationParameter(); + double otherBesselRatio = vmfDistr->GetBesselRatio(); + + double thisToOtherDist = 0.0; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + thisToOtherDist += (m_ConcentrationParameter * m_MeanDirection[i] - otherConcentrationParameter * otherMeanDirection[i]) * m_MeanDirection[i]; + thisToOtherDist *= m_BesselRatio; + + double otherToThisDist = 0.0; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + otherToThisDist += (otherConcentrationParameter * otherMeanDirection[i] - m_ConcentrationParameter * m_MeanDirection[i]) * otherMeanDirection[i]; + otherToThisDist *= otherBesselRatio; + + return thisToOtherDist + otherToThisDist; + } + + vnl_matrix VonMisesFisherDistribution::GetCovarianceMatrix() + { + /** + * \fn vnl_matrix VonMisesFisherDistribution::GetCovarianceMatrix() + * + * \author Aymeric Stamm + * \date November 2023 + * + * \return A numeric matrix of size `m_AmbientDimension x m_AmbientDimension` storing the + * covariance matrix of the von Mises Fisher distribution. This is achieved following + * Kitagawa & Rowley (2022), von Mises-Fisher distributions and their statistical divergence, + * arXiv:2202.05192v1 (https://arxiv.org/pdf/2202.05192v1.pdf). + */ + + vnl_matrix covarianceMatrix(m_AmbientDimension, m_AmbientDimension); + double diagConstant = m_BesselRatio / m_ConcentrationParameter; + double offDiagConstant = 1.0 - static_cast(m_AmbientDimension) * m_BesselRatio / m_ConcentrationParameter - m_BesselRatio * m_BesselRatio; + + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + { + covarianceMatrix(i, i) = diagConstant + offDiagConstant * m_MeanDirection[i] * m_MeanDirection[i]; + + for (unsigned int j = i + 1; j < m_AmbientDimension; ++j) + { + double tmpValue = offDiagConstant * m_MeanDirection[i] * m_MeanDirection[j]; + covarianceMatrix(i, j) = tmpValue; + covarianceMatrix(j, i) = tmpValue; + } + } + + return covarianceMatrix; + } + + void VonMisesFisherDistribution::SampleFromVMFDistribution(ValueType &resVec, GeneratorType &generator) + { + /** + * \fn void VonMisesFisherDistribution::SampleFromVMFDistribution(&resVec, &generator) + * + * \brief Sample from the VMF distribution following Ulrich, G. (1984). Computer generation of + * distributions on the m‐sphere. Journal of the Royal Statistical Society: Series C (Applied + * Statistics), 33(2), 158-163. + * + * \author Aymeric Stamm + * \date October 2013 + * + * \param resVec An object of type itk::Vector that will store a value sampled from + * the VMF distribution. + * \param generator An object of type std::mt19937 specifying which random number generator to + * use. + */ + + ValueType tmpVec; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + tmpVec[i] = 0; + tmpVec[2] = 1.0; + + // Compute rotation matrix to bring [0,0,1] on meanDirection + itk::Matrix rotationMatrix = anima::GetRotationMatrixFromVectors(tmpVec, m_MeanDirection); + + // Now resuming onto sampling around direction [0,0,1] + UniformDistributionType unifDistr(0.0, 1.0); + BetaDistributionType betaDistr(1.0, 1.0); + + double tmpVal = std::sqrt(m_ConcentrationParameter * m_ConcentrationParameter + 1.0); + double b = (-2.0 * m_ConcentrationParameter + 2.0 * tmpVal) / 2.0; + double a = (1.0 + m_ConcentrationParameter + tmpVal) / 2.0; + double d = 4.0 * a * b / (1.0 + b) - 2.0 * std::log(2.0); + + double T = 1.0; + double U = std::exp(d); + double W = 0; + + while (2.0 * std::log(T) - T + d < std::log(U)) + { + double Z = boost::math::quantile(betaDistr, unifDistr(generator)); + U = unifDistr(generator); + tmpVal = 1.0 - (1.0 - b) * Z; + T = 2.0 * a * b / tmpVal; + W = (1.0 - (1.0 + b) * Z) / tmpVal; + } + + double theta = 2.0 * M_PI * unifDistr(generator); + tmpVec[0] = std::sqrt(1.0 - W * W) * std::cos(theta); + tmpVec[1] = std::sqrt(1.0 - W * W) * std::sin(theta); + tmpVec[2] = W; + + // Rotate to bring everthing back around meanDirection + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + { + resVec[j] = 0.0; + for (unsigned int k = 0; k < m_AmbientDimension; ++k) + resVec[j] += rotationMatrix(j, k) * tmpVec[k]; + } + + double resNorm = resVec.Normalize(); + + if (std::abs(resNorm - 1.0) > this->GetEpsilon()) + { + std::cout << "Sampled direction norm: " << resNorm << std::endl; + std::cout << "Mean direction: " << m_MeanDirection << std::endl; + std::cout << "Concentration parameter: " << m_ConcentrationParameter << std::endl; + throw itk::ExceptionObject(__FILE__, __LINE__, "The VMF sampler should generate points on the 2-sphere.", ITK_LOCATION); + } + } + + void VonMisesFisherDistribution::SampleFromVMFDistributionNumericallyStable(ValueType &resVec, GeneratorType &generator) + { + /** + * \fn void VonMisesFisherDistribution::SampleFromVMFDistributionNumericallyStable(&resVec, &generator) + * + * \brief Sample from the VMF distribution following Jakob, W. (2012). Numerically stable sampling + * of the von Mises-Fisher distribution on Sˆ2 (and other tricks). Interactive Geometry Lab, ETH + * Zürich, Tech. Rep, 6. + * + * \author Aymeric Stamm + * \date October 2013 + * + * \param resVec An object of type itk::Vector that will store a value sampled from + * the VMF distribution. + * \param generator An object of type std::mt19937 specifying which random number generator to + * use. + */ + + ValueType tmpVec; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + tmpVec[i] = 0; + tmpVec[2] = 1.0; + + // Compute rotation matrix to bring [0,0,1] on meanDirection + itk::Matrix rotationMatrix = anima::GetRotationMatrixFromVectors(tmpVec, m_MeanDirection); + + // Now resuming onto sampling around direction [0,0,1] + UniformDistributionType unifDistr(0.0, 1.0); + + double xi = unifDistr(generator); + double W = 1.0 + (std::log(xi) + std::log(1.0 - (xi - 1.0) * exp(-2.0 * m_ConcentrationParameter) / xi)) / m_ConcentrationParameter; + double theta = 2.0 * M_PI * unifDistr(generator); + + tmpVec[0] = std::sqrt(1.0 - W * W) * std::cos(theta); + tmpVec[1] = std::sqrt(1.0 - W * W) * std::sin(theta); + tmpVec[2] = W; + + // Rotate to bring everthing back around meanDirection + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + { + resVec[j] = 0.0; + for (unsigned int k = 0; k < m_AmbientDimension; ++k) + resVec[j] += rotationMatrix(j, k) * tmpVec[k]; + } + + double resNorm = resVec.Normalize(); + + if (std::abs(resNorm - 1.0) > this->GetEpsilon()) + { + std::cout << "Sampled direction norm: " << resNorm << std::endl; + std::cout << "Mean direction: " << m_MeanDirection << std::endl; + std::cout << "Concentration parameter: " << m_ConcentrationParameter << std::endl; + throw itk::ExceptionObject(__FILE__, __LINE__, "The VMF sampler should generate points on the 2-sphere.", ITK_LOCATION); + } + } + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.h b/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.h new file mode 100644 index 000000000..0db9b01c4 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaVonMisesFisherDistribution.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include + +#include +#include + +#include + +namespace anima +{ + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT VonMisesFisherDistribution : public BaseDistribution> + { + public: + using UniformDistributionType = std::uniform_real_distribution; + using BetaDistributionType = boost::math::beta_distribution; + + VonMisesFisherDistribution() + { + m_MeanDirection[0] = 0; + m_MeanDirection[1] = 0; + m_MeanDirection[2] = 1; + m_ConcentrationParameter = 1.0; + m_BesselRatio = 1.0; + } + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean() { return m_BesselRatio * m_MeanDirection; } + double GetVariance() { return vnl_trace(this->GetCovarianceMatrix()); } + double GetDistance(Self *otherDistribution); + + void SetMeanDirection(const ValueType &x); + ValueType GetMeanDirection() { return m_MeanDirection; } + + void SetConcentrationParameter(const double &x); + double GetConcentrationParameter() { return m_ConcentrationParameter; } + + double GetBesselRatio() { return m_BesselRatio; } + vnl_matrix GetCovarianceMatrix(); + + private: + void SampleFromVMFDistribution(ValueType &resVec, GeneratorType &generator); + void SampleFromVMFDistributionNumericallyStable(ValueType &resVec, GeneratorType &generator); + ValueType m_MeanDirection; + double m_ConcentrationParameter; + double m_BesselRatio; + const unsigned int m_AmbientDimension = 3; + }; + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaWatsonDistribution.cxx b/Anima/math-tools/statistical_distributions/animaWatsonDistribution.cxx new file mode 100644 index 000000000..12b7585c0 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaWatsonDistribution.cxx @@ -0,0 +1,484 @@ +#include "animaWatsonDistribution.h" +#include +#include +#include + +#include +#include + +namespace anima +{ + + bool WatsonDistribution::BelongsToSupport(const ValueType &x) + { + return std::abs(x.GetNorm() - 1.0) < this->GetEpsilon(); + } + + void WatsonDistribution::SetMeanAxis(const ValueType &val) + { + if (!this->BelongsToSupport(val)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The mean axis parameter of the Watson distribution should be of unit norm.", ITK_LOCATION); + m_MeanAxis[0] = 0.0; + m_MeanAxis[1] = 0.0; + m_MeanAxis[2] = 1.0; + // Compute rotation matrix to bring [0,0,1] on meanAxis + m_NorthToMeanAxisRotationMatrix = anima::GetRotationMatrixFromVectors(m_MeanAxis, val); + m_MeanAxis = val; + } + + void WatsonDistribution::SetConcentrationParameter(const double &val) + { + m_ConcentrationParameter = val; + } + + double WatsonDistribution::GetDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + return 0.0; + + // Case 1: k = 0 - Uniform distribution on the 2-sphere + if (std::abs(m_ConcentrationParameter) <= this->GetEpsilon()) + return 1.0 / (4.0 * M_PI); + + // Case 2: k > 0 - Anisotropic distribution on the 2-sphere + if (m_ConcentrationParameter > this->GetEpsilon()) + { + double kappaSqrt = std::sqrt(m_ConcentrationParameter); + double c = anima::ComputeScalarProduct(x, m_MeanAxis); + double inExp = m_ConcentrationParameter * (c * c - 1.0); + return kappaSqrt * std::exp(inExp) / (4.0 * M_PI * anima::EvaluateDawsonFunctionNR(kappaSqrt)); + } + + // Case 3: k < 0 - Gridle distribution on the 2-sphere + double Ck = std::sqrt(-m_ConcentrationParameter / M_PI) / (2.0 * M_PI * std::erf(std::sqrt(-m_ConcentrationParameter))); + double c = anima::ComputeScalarProduct(x, m_MeanAxis); + double inExp = m_ConcentrationParameter * c * c; + return Ck * std::exp(inExp); + } + + double WatsonDistribution::GetLogDensity(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The log-density of the Watson distribution is not defined for arguments outside the 2-sphere.", ITK_LOCATION); + + return std::log(this->GetDensity(x)); + } + + double WatsonDistribution::GetCumulative(const ValueType &x) + { + if (!this->BelongsToSupport(x)) + throw itk::ExceptionObject(__FILE__, __LINE__, "The CDF is not defined outside the support.", ITK_LOCATION); + + ValueType carCoords, sphCoords; + carCoords.Fill(0.0); + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + carCoords[i] += m_NorthToMeanAxisRotationMatrix(j, i) * x[j]; + anima::TransformCartesianToSphericalCoordinates(carCoords, sphCoords); + + double thetaVal = sphCoords[0]; + while (thetaVal > M_PI) + thetaVal -= (2.0 * M_PI); + while (thetaVal < 0) + thetaVal += (2.0 * M_PI); + double phiVal = sphCoords[1]; + while (phiVal > 2.0 * M_PI) + phiVal -= (2.0 * M_PI); + while (phiVal < 0) + phiVal += 2.0 * M_PI; + + double phiCumul = phiVal / (2.0 * M_PI); + + double cosTheta = std::cos(thetaVal); + double sqrtKappa = std::sqrt(m_ConcentrationParameter); + double dawsonValue = anima::EvaluateDawsonIntegral(sqrtKappa, true); + double dawsonCosValue = anima::EvaluateDawsonIntegral(sqrtKappa * cosTheta, true); + double thetaCumul = dawsonValue; + thetaCumul -= cosTheta * std::exp(-m_ConcentrationParameter * (1.0 - cosTheta * cosTheta)) * dawsonCosValue; + thetaCumul /= 2.0; + thetaCumul /= anima::GetScaledKummerFunctionValue(m_ConcentrationParameter, 0.5, 1.5); + + return phiCumul * thetaCumul; + } + + double WatsonDistribution::ComputeConcentrationMLE(const double rValue, const double aValue, const double cValue, double &logLik) + { + double bBoundValue = (rValue * cValue - aValue) / (2.0 * rValue * (1.0 - rValue)); + double rootInValue = 1.0 + (4.0 * (cValue + 1.0) * rValue * (1.0 - rValue)) / (aValue * (cValue - aValue)); + bBoundValue *= (1.0 + std::sqrt(rootInValue)); + + double lBoundValue = (rValue * cValue - aValue) / (rValue * (1.0 - rValue)); + lBoundValue *= (1.0 + (1.0 - rValue) / (cValue - aValue)); + + double uBoundValue = (rValue * cValue - aValue) / (rValue * (1.0 - rValue)); + uBoundValue *= (1.0 + rValue / aValue); + + double concentrationParameter = 0.0; + if (rValue > aValue / cValue) + concentrationParameter = (bBoundValue + lBoundValue) / 2.0; + if (rValue < aValue / cValue) + concentrationParameter = (bBoundValue + uBoundValue) / 2.0; + + logLik = concentrationParameter * (rValue - 1.0) - std::log(anima::GetScaledKummerFunctionValue(concentrationParameter, aValue, cValue)); + + return concentrationParameter; + } + + void WatsonDistribution::Fit(const SampleType &sample, const std::string &method) + { + /********************************************************************************************** + * \fn void Fit(std::vector>, std::mt19937 &generator) + * + * \brief Closed-form approximations of the maximum likelihood estimators for the mean axis + * and the concentration parameter of the Watson distribution using the procedure + * described in Sra & Karp, The multivariate Watson distribution: Maximum-likelihood + * estimation and other aspects, Journal of Multivariate Analysis, 2013, Vol. 114, + * pp. 256-69. + * + * \author Aymeric Stamm + * \date October 2023 + * + * \param sample A numeric matrix of shape n x 3 specifying a sample of size n drawn from the + * Watson distribution on the 2-sphere. + * \param method A string specifying the estimation method. Unused here. + **********************************************************************************************/ + + unsigned int numberOfObservations = sample.size(); + ValueType meanAxis; + meanAxis.Fill(0.0); + meanAxis[2] = 1.0; + double concentrationParameter = 0.0; + + using MatrixType = itk::Matrix; + MatrixType scatterMatrix; + scatterMatrix.Fill(0.0); + for (unsigned int i = 0; i < numberOfObservations; ++i) + { + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + { + for (unsigned int k = j; k < m_AmbientDimension; ++k) + { + double tmpValue = sample[i][j] * sample[i][k]; + scatterMatrix(j, k) += tmpValue; + if (j != k) + scatterMatrix(k, j) += tmpValue; + } + } + } + + scatterMatrix /= static_cast(numberOfObservations); + + // Eigen decomposition of S + itk::SymmetricEigenAnalysis eigenDecomp(m_AmbientDimension); + ValueType eigenVals; + MatrixType eigenVecs; + eigenDecomp.ComputeEigenValuesAndVectors(scatterMatrix, eigenVals, eigenVecs); + + double aValue = 0.5; + double cValue = static_cast(m_AmbientDimension) / 2.0; + + // Compute estimate of mean axis assuming estimated concentration parameter is positive + double bipolarRValue = 0.0; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + { + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + bipolarRValue += eigenVecs(2, i) * scatterMatrix(i, j) * eigenVecs(2, j); + } + + double bipolarLogLik; + double bipolarKappa = ComputeConcentrationMLE(bipolarRValue, aValue, cValue, bipolarLogLik); + bool bipolarValid = bipolarKappa > 0; + + // Compute estimate of mean axis assuming estimated concentration parameter is negative + double gridleRValue = 0.0; + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + { + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + gridleRValue += eigenVecs(0, i) * scatterMatrix(i, j) * eigenVecs(0, j); + } + + double gridleLogLik; + double gridleKappa = ComputeConcentrationMLE(gridleRValue, aValue, cValue, gridleLogLik); + bool gridleValid = gridleKappa < 0; + + if (bipolarValid && gridleValid) + { + if (gridleLogLik > bipolarLogLik) + { + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + meanAxis[i] = eigenVecs(0, i); + concentrationParameter = gridleKappa; + m_RValue = gridleRValue; + } + else + { + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + meanAxis[i] = eigenVecs(2, i); + concentrationParameter = bipolarKappa; + m_RValue = bipolarRValue; + } + } + else if (bipolarValid) + { + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + meanAxis[i] = eigenVecs(2, i); + concentrationParameter = bipolarKappa; + m_RValue = bipolarRValue; + } + else if (gridleValid) + { + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + meanAxis[i] = eigenVecs(0, i); + concentrationParameter = bipolarKappa; + m_RValue = gridleRValue; + } + else + { + concentrationParameter = 0.0; + m_RValue = gridleRValue; + } + + meanAxis.Normalize(); + + this->SetMeanAxis(meanAxis); + this->SetConcentrationParameter(concentrationParameter); + } + + void WatsonDistribution::Random(SampleType &sample, GeneratorType &generator) + { + /********************************************************************************************** + * \fn void Random(std::vector>, std::mt19937 &generator) + * + * \brief Sample from the Watson distribution using the procedure described in Fisher et al., + * Statistical Analysis of Spherical Data, Cambridge University Press, 1993, pp. 59. + * + * \author Aymeric Stamm + * \date October 2013 + * + * \param sample A numeric matrix of shape n x 3 storing a sample of size n drawn from the + * Watson distribution on the 2-sphere. + * \param generator A pseudo-random number generator. + **********************************************************************************************/ + + ValueType tmpVec, resVec; + + // Now resuming onto sampling around direction [0,0,1] + double U, V, S; + UniformDistributionType distributionValue(0.0, 1.0); + unsigned int nSamples = sample.size(); + + for (unsigned int i = 0; i < nSamples; ++i) + { + if (m_ConcentrationParameter > std::sqrt(std::numeric_limits::epsilon())) // Bipolar distribution + { + U = distributionValue(generator); + S = 1.0 + std::log(U + (1.0 - U) * std::exp(-m_ConcentrationParameter)) / m_ConcentrationParameter; + + V = distributionValue(generator); + + if (V > 1.0e-6) + { + while (std::log(V) > m_ConcentrationParameter * S * (S - 1.0)) + { + U = distributionValue(generator); + S = 1.0 + std::log(U + (1.0 - U) * std::exp(-m_ConcentrationParameter)) / m_ConcentrationParameter; + + V = distributionValue(generator); + + if (V < 1.0e-6) + break; + } + } + } + else if (m_ConcentrationParameter < -std::sqrt(std::numeric_limits::epsilon())) // Gridle distribution + { + double C1 = std::sqrt(std::abs(m_ConcentrationParameter)); + double C2 = std::atan(C1); + U = distributionValue(generator); + V = distributionValue(generator); + S = (1.0 / C1) * std::tan(C2 * U); + + double T = m_ConcentrationParameter * S * S; + while (V > (1.0 - T) * std::exp(T)) + { + U = distributionValue(generator); + V = distributionValue(generator); + S = (1.0 / C1) * std::tan(C2 * U); + T = m_ConcentrationParameter * S * S; + } + } + else // Uniform distribution + S = std::cos(M_PI * distributionValue(generator)); + + double phi = 2.0 * M_PI * distributionValue(generator); + + tmpVec[0] = std::sqrt(1.0 - S * S) * std::cos(phi); + tmpVec[1] = std::sqrt(1.0 - S * S) * std::sin(phi); + tmpVec[2] = S; + + // Rotate to bring everthing back around meanAxis + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + { + resVec[j] = 0.0; + for (unsigned int k = 0; k < m_AmbientDimension; ++k) + resVec[j] += m_NorthToMeanAxisRotationMatrix(j, k) * tmpVec[k]; + } + + double resNorm = resVec.Normalize(); + + if (std::abs(resNorm - 1.0) > this->GetEpsilon()) + { + std::cout << "Sampled direction norm: " << resNorm << std::endl; + std::cout << "Mean direction: " << m_MeanAxis << std::endl; + std::cout << "Concentration parameter: " << m_ConcentrationParameter << std::endl; + throw itk::ExceptionObject(__FILE__, __LINE__, "The Watson sampler should generate points on the 2-sphere.", ITK_LOCATION); + } + + sample[i] = resVec; + } + } + + WatsonDistribution::ValueType WatsonDistribution::GetMean() + { + ValueType meanValue; + meanValue.Fill(0.0); + return meanValue; + } + + vnl_matrix WatsonDistribution::GetCovarianceMatrix() + { + /** + * \fn vnl_matrix WatsonDistribution::GetCovarianceMatrix() + * + * \author Aymeric Stamm + * \date November 2023 + * + * \return A numeric matrix of size `m_AmbientDimension x m_AmbientDimension` storing the + * covariance matrix of the Watson distribution. + */ + + vnl_matrix covarianceMatrix(m_AmbientDimension, m_AmbientDimension, 0.0); + + if (std::abs(m_ConcentrationParameter) < this->GetEpsilon()) + { + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + covarianceMatrix.put(i, i, 1.0 / 3.0); + return covarianceMatrix; + } + + vnl_matrix tmpMatrix(m_AmbientDimension, m_AmbientDimension, 0.0); + + double sqrtKappa = std::sqrt(m_ConcentrationParameter); + double dawsonValue = anima::EvaluateDawsonIntegral(sqrtKappa, true); + double kummerValue = anima::GetScaledKummerFunctionValue(m_ConcentrationParameter, 0.5, 1.5); + + double tmpValue = (1.0 - dawsonValue) / (2.0 * m_ConcentrationParameter * kummerValue); + tmpMatrix.put(2, 2, tmpValue); + + tmpValue = (2.0 * dawsonValue - (1.0 - dawsonValue) / m_ConcentrationParameter) / (4.0 * kummerValue); + tmpMatrix.put(0, 0, tmpValue); + tmpMatrix.put(1, 1, tmpValue); + + for (unsigned int i = 0; i < m_AmbientDimension; ++i) + for (unsigned int j = 0; j < m_AmbientDimension; ++j) + for (unsigned int k = 0; k < m_AmbientDimension; ++k) + for (unsigned int l = 0; l < m_AmbientDimension; ++l) + covarianceMatrix(i, j) += m_NorthToMeanAxisRotationMatrix(i, k) * tmpMatrix(k, l) * m_NorthToMeanAxisRotationMatrix(j, l); + + return covarianceMatrix; + } + + void WatsonDistribution::GetStandardWatsonSHCoefficients(std::vector &coefficients, + std::vector &derivatives) + { + // Computes the first 7 non-zero SH coefficients of the standard Watson PDF (multiplied by 4 M_PI). + const unsigned int nbCoefs = 7; + coefficients.resize(nbCoefs); + derivatives.resize(nbCoefs); + + double sqrtPi = std::sqrt(M_PI); + double k = m_ConcentrationParameter; + double dawsonValue = anima::EvaluateDawsonIntegral(std::sqrt(k), true); + double k2 = k * k; + double k3 = k2 * k; + double k4 = k3 * k; + double k5 = k4 * k; + double k6 = k5 * k; + double k7 = k6 * k; + + coefficients[0] = 2.0 * sqrtPi; + derivatives[0] = 0.0; + + if (k < 1.0e-4) // Handles small k values by Taylor series expansion + { + coefficients[1] = k / 3.0; + coefficients[2] = k2 / 35.0; + coefficients[3] = k3 / 693.0; + coefficients[4] = k4 / 19305.0; + coefficients[5] = k5 / 692835.0; + coefficients[6] = k6 / 30421755.0; + derivatives[1] = 1.0 / 3.0; + derivatives[2] = 2.0 * k / 35.0; + derivatives[3] = k2 / 231.0; + derivatives[4] = 4.0 * k3 / 19305.0; + derivatives[5] = k4 / 138567.0; + derivatives[6] = 6.0 * k5 / 30421755.0; + + for (unsigned int i = 1; i < nbCoefs; ++i) + { + double tmpVal = std::pow(2.0, i + 1.0) * sqrtPi / std::sqrt(4.0 * i + 1.0); + coefficients[i] *= tmpVal; + derivatives[i] *= tmpVal; + } + + return; + } + + coefficients[1] = (3.0 - (3.0 + 2.0 * k) * dawsonValue) / (2.0 * k); + coefficients[2] = (5.0 * (-21.0 + 2.0 * k) + 3.0 * (35.0 + 4.0 * k * (5.0 + k)) * dawsonValue) / (16.0 * k2); + coefficients[3] = (21.0 * (165.0 + 4.0 * (-5.0 + k) * k) - 5.0 * (693.0 + 378.0 * k + 84.0 * k2 + 8.0 * k3) * dawsonValue) / (64.0 * k3); + coefficients[4] = (3.0 * (-225225.0 + 2.0 * k * (15015.0 + 2.0 * k * (-1925.0 + 62.0 * k))) + 35.0 * (19305.0 + 8.0 * k * (1287.0 + k * (297.0 + 2.0 * k * (18.0 + k)))) * dawsonValue) / (1024.0 * k4); + coefficients[5] = (11.0 * (3968055.0 + 8.0 * k * (-69615.0 + 2.0 * k * (9828.0 + k * (-468.0 + 29.0 * k)))) - 63.0 * (692835.0 + 2.0 * k * (182325.0 + 4.0 * k * (10725.0 + 2.0 * k * (715.0 + k * (55.0 + 2.0 * k))))) * dawsonValue) / (4096.0 * k5); + coefficients[6] = (13.0 * (-540571185.0 + 2.0 * k * (39171825.0 + 4.0 * k * (-2909907.0 + 2.0 * k * (82467.0 + k * (-7469.0 + 122.0 * k))))) + 231.0 * (30421755.0 + 4.0 * k * (3968055.0 + k * (944775.0 + 4.0 * k * (33150.0 + k * (2925.0 + 4.0 * k * (39.0 + k)))))) * dawsonValue) / (32768.0 * k6); + + derivatives[1] = 3.0 * (-1.0 + (-1.0 + 2.0 * k) * dawsonValue + 2.0 * dawsonValue * dawsonValue) / (4.0 * k2); + derivatives[2] = 5.0 * ((21.0 - 2.0 * k) + (63.0 + 4.0 * (-11.0 + k) * k) * dawsonValue - 12.0 * (7.0 + 2.0 * k) * dawsonValue * dawsonValue) / (32.0 * k3); + derivatives[3] = 21.0 * ((-165.0 - 4.0 * (-5.0 + k) * k) + (-5.0 + 2.0 * k) * (165.0 + 4.0 * (-3.0 + k) * k) * dawsonValue + 10.0 * (99.0 + 4.0 * k * (9.0 + k)) * dawsonValue * dawsonValue) / (128.0 * k4); + derivatives[4] = 3.0 * ((225225.0 - 2.0 * k * (15015.0 + 2.0 * k * (-1925.0 + 62.0 * k))) + (1576575.0 + 8.0 * k * (-75075.0 + k * (10395.0 + 2.0 * k * (-978.0 + 31.0 * k)))) * dawsonValue - 840.0 * (2145.0 + 2.0 * k * (429.0 + 66.0 * k + 4.0 * k2)) * dawsonValue * dawsonValue) / (2048.0 * k5); + derivatives[5] = 11.0 * ((-3968055.0 - 8.0 * k * (-69615.0 + 2.0 * k * (9828.0 + k * (-468.0 + 29.0 * k)))) + (-35712495.0 + 2.0 * k * (5917275.0 + 8.0 * k * (-118755.0 + k * (21060.0 + k * (-965.0 + 58.0 * k))))) * dawsonValue + 630.0 * (62985.0 + 8.0 * k * (3315.0 + k * (585.0 + 2.0 * k * (26.0 + k)))) * dawsonValue * dawsonValue) / (8192.0 * k6); + derivatives[6] = 13.0 * ((540571185.0 - 2.0 * k * (39171825.0 + 4.0 * k * (-2909907.0 + 2.0 * k * (82467.0 + k * (-7469.0 + 122.0 * k))))) + (5946283035.0 + 4.0 * k * (-446558805.0 + k * (79910523.0 + 4.0 * k * (-3322242.0 + k * (187341.0 + 4.0 * k * (-3765.0 + 61.0 * k)))))) * dawsonValue - 2772.0 * (2340135.0 + 2.0 * k * (508725.0 + 4.0 * k * (24225.0 + 2.0 * k * (1275.0 + k * (75.0 + 2.0 * k))))) * dawsonValue * dawsonValue) / (65536.0 * k7); + + for (unsigned int i = 1; i < nbCoefs; ++i) + { + double sqrtVal = std::sqrt(1.0 + 4.0 * i); + coefficients[i] *= (sqrtPi * sqrtVal / dawsonValue); + derivatives[i] *= (sqrtPi * sqrtVal / (dawsonValue * dawsonValue)); + } + } + + double WatsonDistribution::GetDistance(Self *otherDistribution) + { + const unsigned int numberOfMonteCarloSamples = 10000; + SampleType thisWatsonSample(numberOfMonteCarloSamples), otherWatsonSample(numberOfMonteCarloSamples); + GeneratorType generator; + + this->Random(thisWatsonSample, generator); + WatsonDistribution *watsonDistr = dynamic_cast(otherDistribution); + watsonDistr->Random(otherWatsonSample, generator); + + double thisKLValue = 0.0, otherKLValue = 0.0; + for (unsigned int i = 0; i < numberOfMonteCarloSamples; ++i) + { + thisKLValue += this->GetLogDensity(thisWatsonSample[i]); + thisKLValue -= watsonDistr->GetLogDensity(thisWatsonSample[i]); + otherKLValue += watsonDistr->GetLogDensity(otherWatsonSample[i]); + otherKLValue -= this->GetLogDensity(otherWatsonSample[i]); + } + + thisKLValue /= static_cast(numberOfMonteCarloSamples); + otherKLValue /= static_cast(numberOfMonteCarloSamples); + + return thisKLValue + otherKLValue; + } + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaWatsonDistribution.h b/Anima/math-tools/statistical_distributions/animaWatsonDistribution.h index 099279bae..b4dc1e80f 100644 --- a/Anima/math-tools/statistical_distributions/animaWatsonDistribution.h +++ b/Anima/math-tools/statistical_distributions/animaWatsonDistribution.h @@ -1,27 +1,54 @@ #pragma once -#include -#include +#include + +#include #include namespace anima { - - template - double EvaluateWatsonPDF(const VectorType &v, const VectorType &meanAxis, const ScalarType &kappa); - - template - double EvaluateWatsonPDF(const vnl_vector_fixed &v, const vnl_vector_fixed &meanAxis, const ScalarType &kappa); - - template - double EvaluateWatsonPDF(const itk::Point &v, const itk::Point &meanAxis, const ScalarType &kappa); - - template - double EvaluateWatsonPDF(const itk::Vector &v, const itk::Vector &meanAxis, const ScalarType &kappa); - - template - void GetStandardWatsonSHCoefficients(const ScalarType k, std::vector &coefficients, std::vector &derivatives); - -} // end of namespace anima + class ANIMASTATISTICALDISTRIBUTIONS_EXPORT WatsonDistribution : public BaseDistribution> + { + public: + using UniformDistributionType = std::uniform_real_distribution; + + WatsonDistribution() + { + m_MeanAxis[0] = 0; + m_MeanAxis[1] = 0; + m_MeanAxis[2] = 1; + m_ConcentrationParameter = 1.0; + m_RValue = 0.0; + } + + bool BelongsToSupport(const ValueType &x); + double GetDensity(const ValueType &x); + double GetLogDensity(const ValueType &x); + double GetCumulative(const ValueType &x); + void Fit(const SampleType &sample, const std::string &method); + void Random(SampleType &sample, GeneratorType &generator); + ValueType GetMean(); + double GetVariance() { return 1.0 - m_RValue; } + double GetDistance(Self *otherDistribution); + + void SetMeanAxis(const ValueType &x); + ValueType GetMeanAxis() { return m_MeanAxis; } -#include "animaWatsonDistribution.hxx" + void SetConcentrationParameter(const double &x); + double GetConcentrationParameter() { return m_ConcentrationParameter; } + + vnl_matrix GetCovarianceMatrix(); + void GetStandardWatsonSHCoefficients( + std::vector &coefficients, + std::vector &derivatives); + + private: + double ComputeConcentrationMLE(const double rValue, const double aValue, const double cValue, double &logLik); + ValueType m_MeanAxis; + double m_ConcentrationParameter; + double m_RValue; + itk::Matrix m_NorthToMeanAxisRotationMatrix; + const unsigned int m_AmbientDimension = 3; + }; + +} // end of namespace anima diff --git a/Anima/math-tools/statistical_distributions/animaWatsonDistribution.hxx b/Anima/math-tools/statistical_distributions/animaWatsonDistribution.hxx deleted file mode 100644 index ea40ae519..000000000 --- a/Anima/math-tools/statistical_distributions/animaWatsonDistribution.hxx +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once -#include - -#include "animaWatsonDistribution.h" -#include -#include - -#include -#include - -#include - -namespace anima -{ - -template -double EvaluateWatsonPDF(const VectorType &v, const VectorType &meanAxis, const ScalarType &kappa) -{ - /************************************************************************************************ - * \fn template - * double - * EvaluateWatsonPDF(const VectorType &v, - * const VectorType &meanAxis, - * const ScalarType &kappa) - * - * \brief Evaluate the Watson probability density function using the definition of - * Fisher et al., Statistical Analysis of Spherical Data, 1993, p.89. - * - * \author Aymeric Stamm - * \date July 2014 - * - * \param v Sample on which evaluating the PDF. - * \param meanAxis Mean axis of the Watson distribution. - * \param kappa Concentration parameter of the Watson distribution. - **************************************************************************************************/ - - if (std::abs(kappa) < 1.0e-6) - return 1.0 / (4.0 * M_PI); - else if (kappa > 0) - { - double kappaSqrt = std::sqrt(kappa); - double c = anima::ComputeScalarProduct(v, meanAxis); - double inExp = kappa * (c * c - 1.0); - return kappaSqrt * std::exp(inExp) / (4.0 * M_PI * anima::EvaluateDawsonFunctionNR(kappaSqrt)); - } - else - { - double Ck = std::sqrt(-kappa / M_PI) / (2.0 * M_PI * std::erf(std::sqrt(-kappa))); - double c = anima::ComputeScalarProduct(v, meanAxis); - double inExp = kappa * c * c; - return Ck * std::exp(inExp); - } -} - -template -double EvaluateWatsonPDF(const vnl_vector_fixed &v, const vnl_vector_fixed &meanAxis, const ScalarType &kappa) -{ - if (std::abs(v.squared_magnitude() - 1.0) > 1.0e-6 || std::abs(meanAxis.squared_magnitude() - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"The Watson distribution is on the 2-sphere.",ITK_LOCATION); - - return EvaluateWatsonPDF, ScalarType>(v,meanAxis,kappa); -} - -template -double EvaluateWatsonPDF(const itk::Point &v, const itk::Point &meanAxis, const ScalarType &kappa) -{ - if (std::abs(v.GetVnlVector().squared_magnitude() - 1.0) > 1.0e-6 || std::abs(meanAxis.GetVnlVector().squared_magnitude() - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"The Watson distribution is on the 2-sphere.",ITK_LOCATION); - - return EvaluateWatsonPDF, ScalarType>(v,meanAxis,kappa); -} - -template -double EvaluateWatsonPDF(const itk::Vector &v, const itk::Vector &meanAxis, const ScalarType &kappa) -{ - if (std::abs(v.GetSquaredNorm() - 1.0) > 1.0e-6 || std::abs(meanAxis.GetSquaredNorm() - 1.0) > 1.0e-6) - throw itk::ExceptionObject(__FILE__, __LINE__,"The Watson distribution is on the 2-sphere.",ITK_LOCATION); - - return EvaluateWatsonPDF, ScalarType>(v,meanAxis,kappa); -} - -template - void GetStandardWatsonSHCoefficients(const ScalarType k, std::vector &coefficients, std::vector &derivatives) -{ - // Computes the first 7 non-zero SH coefficients of the standard Watson PDF (multiplied by 4 M_PI). - const unsigned int nbCoefs = 7; - coefficients.resize(nbCoefs); - derivatives.resize(nbCoefs); - - double sqrtPi = std::sqrt(M_PI); - double dawsonValue = anima::EvaluateDawsonIntegral(std::sqrt(k), true); - double k2 = k * k; - double k3 = k2 * k; - double k4 = k3 * k; - double k5 = k4 * k; - double k6 = k5 * k; - double k7 = k6 * k; - - coefficients[0] = 2.0 * sqrtPi; - derivatives[0] = 0.0; - - if (k < 1.0e-4) // Handles small k values by Taylor series expansion - { - coefficients[1] = k / 3.0; - coefficients[2] = k2 / 35.0; - coefficients[3] = k3 / 693.0; - coefficients[4] = k4 / 19305.0; - coefficients[5] = k5 / 692835.0; - coefficients[6] = k6 / 30421755.0; - derivatives[1] = 1.0 / 3.0; - derivatives[2] = 2.0 * k / 35.0; - derivatives[3] = k2 / 231.0; - derivatives[4] = 4.0 * k3 / 19305.0; - derivatives[5] = k4 / 138567.0; - derivatives[6] = 6.0 * k5 / 30421755.0; - - for (unsigned int i = 1;i < nbCoefs;++i) - { - double tmpVal = std::pow(2.0, i + 1.0) * sqrtPi / std::sqrt(4.0 * i + 1.0); - coefficients[i] *= tmpVal; - derivatives[i] *= tmpVal; - } - - return; - } - - coefficients[1] = (3.0 - (3.0 + 2.0 * k) * dawsonValue) / (2.0 * k); - coefficients[2] = (5.0 * (-21.0 + 2.0 * k) + 3.0 * (35.0 + 4.0 * k * (5.0 + k)) * dawsonValue) / (16.0 * k2); - coefficients[3] = (21.0 * (165.0 + 4.0 * (-5.0 + k) * k) - 5.0 * (693.0 + 378.0 * k + 84.0 * k2 + 8.0 * k3) * dawsonValue) / (64.0 * k3); - coefficients[4] = (3.0 * (-225225.0 + 2.0 * k * (15015.0 + 2.0 * k * (-1925.0 + 62.0 * k))) + 35.0 * (19305.0 + 8.0 * k * (1287.0 + k * (297.0 + 2.0 * k * (18.0 + k)))) * dawsonValue) / (1024.0 * k4); - coefficients[5] = (11.0 * (3968055.0 + 8.0 * k * (-69615.0 + 2.0 * k * (9828.0 + k * (-468.0 + 29.0 * k)))) - 63.0 * (692835.0 + 2.0 * k * (182325.0 + 4.0 * k * (10725.0 + 2.0 * k * (715.0 + k * (55.0 + 2.0 * k))))) * dawsonValue) / (4096.0 * k5); - coefficients[6] = (13.0 * (-540571185.0 + 2.0 * k * (39171825.0 + 4.0 * k * (-2909907.0 + 2.0 * k * (82467.0 + k * (-7469.0 + 122.0 * k))))) + 231.0 * (30421755.0 + 4.0 * k * (3968055.0 + k * (944775.0 + 4.0 * k * (33150.0 + k * (2925.0 + 4.0 * k * (39.0 + k)))))) * dawsonValue) / (32768.0 * k6); - - derivatives[1] = 3.0 * (-1.0 + (-1.0 + 2.0 * k) * dawsonValue + 2.0 * dawsonValue * dawsonValue) / (4.0 * k2); - derivatives[2] = 5.0 * ((21.0 - 2.0 * k) + (63.0 + 4.0 * (-11.0 + k) * k) * dawsonValue - 12.0 * (7.0 + 2.0 * k) * dawsonValue * dawsonValue) / (32.0 * k3); - derivatives[3] = 21.0 * ((-165.0 - 4.0 * (-5.0 + k) * k) + (-5.0 + 2.0 * k) * (165.0 + 4.0 * (-3.0 + k) * k) * dawsonValue + 10.0 * (99.0 + 4.0 * k * (9.0 + k)) * dawsonValue * dawsonValue) / (128.0 * k4); - derivatives[4] = 3.0 * ((225225.0 - 2.0 * k * (15015.0 + 2.0 * k * (-1925.0 + 62.0 * k))) + (1576575.0 + 8.0 * k * (-75075.0 + k * (10395.0 + 2.0 * k * (-978.0 + 31.0 * k)))) * dawsonValue - 840.0 * (2145.0 + 2.0 * k * (429.0 + 66.0 * k + 4.0 * k2)) * dawsonValue * dawsonValue) / (2048.0 * k5); - derivatives[5] = 11.0 * ((-3968055.0 - 8.0 * k * (-69615.0 + 2.0 * k * (9828.0 + k * (-468.0 + 29.0 * k)))) + (-35712495.0 + 2.0 * k * (5917275.0 + 8.0 * k * (-118755.0 + k * (21060.0 + k * (-965.0 + 58.0 * k))))) * dawsonValue + 630.0 * (62985.0 + 8.0 * k * (3315.0 + k * (585.0 + 2.0 * k * (26.0 + k)))) * dawsonValue * dawsonValue) / (8192.0 * k6); - derivatives[6] = 13.0 * ((540571185.0 - 2.0 * k * (39171825.0 + 4.0 * k * (-2909907.0 + 2.0 * k * (82467.0 + k * (-7469.0 + 122.0 * k))))) + (5946283035.0 + 4.0 * k * (-446558805.0 + k * (79910523.0 + 4.0 * k * (-3322242.0 + k * (187341.0 + 4.0 * k * (-3765.0 + 61.0 * k)))))) * dawsonValue - 2772.0 * (2340135.0 + 2.0 * k * (508725.0 + 4.0 * k * (24225.0 + 2.0 * k * (1275.0 + k * (75.0 + 2.0 * k))))) * dawsonValue * dawsonValue) / (65536.0 * k7); - - for (unsigned int i = 1;i < nbCoefs;++i) - { - double sqrtVal = std::sqrt(1.0 + 4.0 * i); - coefficients[i] *= (sqrtPi * sqrtVal / dawsonValue); - derivatives[i] *= (sqrtPi * sqrtVal / (dawsonValue * dawsonValue)); - } -} - -} // end namespace anima diff --git a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt index 1a9c50218..c38421a46 100644 --- a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} + AnimaStatisticalDistributions ) ## ############################################################################# diff --git a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx index ad4167b95..5996a807f 100644 --- a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx @@ -7,52 +7,52 @@ int main(int argc, char **argv) { - TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); - - TCLAP::ValueArg meanArg("m","mean","Average of the distribution",true,"","Average image of the distribution",cmd); - TCLAP::ValueArg covArg("c","cov","Covariance of the distribution",true,"","covariance image of the distribution",cmd); - TCLAP::ValueArg resArg("o","output","Sample generated from distribution",true,"","sample output image",cmd); - - TCLAP::ValueArg nbpArg("p","numberofthreads","Number of threads to run on (default : all cores)",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"number of threads",cmd); - + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg meanArg("m", "mean", "Average of the distribution", true, "", "Average image of the distribution", cmd); + TCLAP::ValueArg covArg("c", "cov", "Covariance of the distribution", true, "", "covariance image of the distribution", cmd); + TCLAP::ValueArg resArg("o", "output", "Sample generated from distribution", true, "", "sample output image", cmd); + + TCLAP::ValueArg nbpArg("p", "numberofthreads", "Number of threads to run on (default : all cores)", false, itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(), "number of threads", cmd); + try { - cmd.parse(argc,argv); + cmd.parse(argc, argv); } - catch (TCLAP::ArgException& e) + catch (TCLAP::ArgException &e) { std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; - return(1); + return (1); } - - typedef anima::SampleImageFromDistributionImageFilter MainFilterType; - - typedef itk::ImageFileReader < MainFilterType::TInputImage > ImageReaderType; - typedef itk::ImageFileWriter < MainFilterType::TOutputImage > ImageWriterType; + + typedef anima::SampleImageFromDistributionImageFilter MainFilterType; + + typedef itk::ImageFileReader ImageReaderType; + typedef itk::ImageFileWriter ImageWriterType; MainFilterType::Pointer mainFilter = MainFilterType::New(); - + ImageReaderType::Pointer meanReader = ImageReaderType::New(); meanReader->SetFileName(meanArg.getValue()); meanReader->Update(); - mainFilter->SetInput(0,meanReader->GetOutput()); + mainFilter->SetInput(0, meanReader->GetOutput()); ImageReaderType::Pointer covReader = ImageReaderType::New(); covReader->SetFileName(covArg.getValue()); covReader->Update(); - mainFilter->SetInput(1,covReader->GetOutput()); + mainFilter->SetInput(1, covReader->GetOutput()); mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); - + mainFilter->Update(); - + std::cout << "Writing result to : " << resArg.getValue() << std::endl; - + ImageWriterType::Pointer outWriter = ImageWriterType::New(); outWriter->SetInput(mainFilter->GetOutput()); outWriter->SetFileName(resArg.getValue()); outWriter->SetUseCompression(true); - + outWriter->Update(); - + return 0; } diff --git a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.h b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.h new file mode 100644 index 000000000..81e43de4d --- /dev/null +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +namespace anima +{ + + template + class SampleImageFromDistributionImageFilter : public itk::ImageToImageFilter, itk::VectorImage> + { + public: + /** Standard class typedefs. */ + typedef SampleImageFromDistributionImageFilter Self; + typedef itk::VectorImage TInputImage; + typedef itk::VectorImage TOutputImage; + typedef itk::ImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods) */ + itkTypeMacro(SampleImageFromDistributionImageFilter, ImageToImageFilter); + + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TInputImage::PixelType InputImagePixel; + typedef typename TOutputImage::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + protected: + SampleImageFromDistributionImageFilter() + { + m_VectorSize = 3; + } + + virtual ~SampleImageFromDistributionImageFilter() {} + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + private: + ITK_DISALLOW_COPY_AND_ASSIGN(SampleImageFromDistributionImageFilter); + + InputImagePixel m_BaseDistributionSample; + unsigned int m_VectorSize; + }; + +} // end namespace anima + +#include "animaSampleImageFromDistributionImageFilter.hxx" diff --git a/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.hxx b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.hxx new file mode 100644 index 000000000..79fc0028b --- /dev/null +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistributionImageFilter.hxx @@ -0,0 +1,123 @@ +#pragma once + +#include "animaSampleImageFromDistributionImageFilter.h" +#include +#include + +#include + +namespace anima +{ + + template + void + SampleImageFromDistributionImageFilter::GenerateOutputInformation() + { + // Override the method in itkImageSource, so we can set the vector length of + // the output itk::VectorImage + + this->Superclass::GenerateOutputInformation(); + + m_VectorSize = this->GetInput(0)->GetNumberOfComponentsPerPixel(); + TOutputImage *output = this->GetOutput(); + output->SetVectorLength(m_VectorSize); + } + + template + void + SampleImageFromDistributionImageFilter::BeforeThreadedGenerateData() + { + if (this->GetNumberOfIndexedInputs() != 2) + itkExceptionMacro("Two inputs required: average and covariance images"); + + std::mt19937 motherGenerator(time(0)); + using MultivariateNormalDistribution = anima::MultivariateNormalDistribution; + MultivariateNormalDistribution normDistr; + MultivariateNormalDistribution::ValueType meanValue(m_VectorSize, 0.0); + MultivariateNormalDistribution::MatrixType covValue(m_VectorSize, m_VectorSize); + MultivariateNormalDistribution::SampleType sampleValues(1); + normDistr.SetMeanParameter(meanValue); + covValue.set_identity(); + normDistr.SetCovarianceMatrixParameter(covValue); + + normDistr.Random(sampleValues, motherGenerator); + + m_BaseDistributionSample = InputImagePixel(m_VectorSize); + for (unsigned int i = 0; i < m_VectorSize; ++i) + m_BaseDistributionSample[i] = sampleValues[0][i]; + } + + template + void + SampleImageFromDistributionImageFilter::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) + { + typedef itk::ImageRegionConstIterator InputImageConstIteratorType; + typedef itk::ImageRegionIterator InputImageIteratorType; + + InputImageConstIteratorType meanItr(this->GetInput(0), outputRegionForThread); + InputImageConstIteratorType covItr(this->GetInput(1), outputRegionForThread); + InputImageIteratorType outItr(this->GetOutput(), outputRegionForThread); + + InputImagePixel sample(m_VectorSize), covLine, meanLine; + vnl_matrix covMat(m_VectorSize, m_VectorSize); + vnl_matrix eVecs(m_VectorSize, m_VectorSize); + vnl_diag_matrix eVals(m_VectorSize); + + itk::SymmetricEigenAnalysis, vnl_diag_matrix, vnl_matrix> eigenComputer(m_VectorSize); + + while (!outItr.IsAtEnd()) + { + covLine = covItr.Get(); + meanLine = meanItr.Get(); + sample.Fill(0); + + unsigned int pos = 0; + bool nullMat = true; + for (unsigned int i = 0; i < m_VectorSize; ++i) + for (unsigned int j = i; j < m_VectorSize; ++j) + { + if (covLine[pos] != 0) + nullMat = false; + covMat(i, j) = covLine[pos]; + if (i != j) + covMat(j, i) = covMat(i, j); + + ++pos; + } + + if (nullMat) + { + outItr.Set(sample); + ++meanItr; + ++covItr; + ++outItr; + + continue; + } + + eigenComputer.ComputeEigenValuesAndVectors(covMat, eVals, eVecs); + + for (unsigned int i = 0; i < m_VectorSize; ++i) + eVals[i] = sqrt(eVals[i]); + + anima::RecomposeTensor(eVals, eVecs, covMat); + + sample = m_BaseDistributionSample; + for (unsigned int i = 0; i < m_VectorSize; ++i) + { + sample[i] = 0; + for (unsigned int j = 0; j < m_VectorSize; ++j) + sample[i] += covMat(i, j) * m_BaseDistributionSample[j]; + + sample[i] += meanLine[i]; + } + + outItr.Set(sample); + + ++meanItr; + ++covItr; + ++outItr; + } + } + +} // end namespace anima diff --git a/Anima/math-tools/statistical_distributions/watson_sh_test/CMakeLists.txt b/Anima/math-tools/statistical_distributions/watson_sh_test/CMakeLists.txt index 26b35c12f..f91fcde00 100644 --- a/Anima/math-tools/statistical_distributions/watson_sh_test/CMakeLists.txt +++ b/Anima/math-tools/statistical_distributions/watson_sh_test/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} AnimaSpecialFunctions + AnimaStatisticalDistributions ) ## ############################################################################# diff --git a/Anima/math-tools/statistical_distributions/watson_sh_test/animaWatsonSHTest.cxx b/Anima/math-tools/statistical_distributions/watson_sh_test/animaWatsonSHTest.cxx index c5b81c08b..2c75f9434 100644 --- a/Anima/math-tools/statistical_distributions/watson_sh_test/animaWatsonSHTest.cxx +++ b/Anima/math-tools/statistical_distributions/watson_sh_test/animaWatsonSHTest.cxx @@ -1,4 +1,6 @@ #include +#include + #include int main(int argc, char **argv) @@ -7,18 +9,25 @@ int main(int argc, char **argv) std::cout << "Testing SH approximation for kappa : " << kappa << std::endl; - std::vector coefs, derivatives; - anima::GetStandardWatsonSHCoefficients(kappa,coefs,derivatives); + std::vector coefs, derivatives; + anima::WatsonDistribution watsonDistr; + itk::Vector meanAxis; + meanAxis[0] = 0.0; + meanAxis[1] = 0.0; + meanAxis[2] = 1.0; + watsonDistr.SetMeanAxis(meanAxis); + watsonDistr.SetConcentrationParameter(kappa); + watsonDistr.GetStandardWatsonSHCoefficients(coefs, derivatives); - std::cout << "Dawson integral: " << anima::EvaluateDawsonIntegral(std::sqrt(kappa),false) << std::endl; + std::cout << "Dawson integral: " << anima::EvaluateDawsonIntegral(std::sqrt(kappa), false) << std::endl; std::cout << "Coefficients: "; - for (unsigned int i = 0;i < coefs.size();++i) + for (unsigned int i = 0; i < coefs.size(); ++i) std::cout << coefs[i] / (4 * M_PI) << " "; std::cout << std::endl; std::cout << "Derivatives: "; - for (unsigned int i = 0;i < coefs.size();++i) + for (unsigned int i = 0; i < coefs.size(); ++i) std::cout << derivatives[i] / (4 * M_PI) << " "; std::cout << std::endl; diff --git a/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx index 71d3175ae..e860a0eb8 100644 --- a/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx +++ b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx @@ -6,11 +6,9 @@ #include #include -#include #include - -#include - +#include +#include namespace anima { @@ -185,6 +183,7 @@ CramersTestImageFilter std::vector sampleGen, sampleGenOtherGroup; std::mt19937 generator(time(0)); + std::uniform_real_distribution unifDistr(0.0, 1.0); for (unsigned int i = 1;i <= m_NbSamples;++i) { @@ -194,7 +193,7 @@ CramersTestImageFilter unsigned int j = 0; while (j < minNbGroup) { - int tmpVal = std::min((int)(nbSubjects - 1),(int)floor(anima::SampleFromUniformDistribution(0.0,1.0,generator) * nbSubjects)); + int tmpVal = std::min((int)(nbSubjects - 1),(int)floor(unifDistr(generator) * nbSubjects)); if (tmpVal < 0) tmpVal = 0; diff --git a/Anima/math-tools/statistics/CMakeLists.txt b/Anima/math-tools/statistics/CMakeLists.txt index 1e8c08d91..f9b5e598e 100644 --- a/Anima/math-tools/statistics/CMakeLists.txt +++ b/Anima/math-tools/statistics/CMakeLists.txt @@ -4,9 +4,12 @@ add_subdirectory(build_samples) add_subdirectory(gamma_estimation) +add_subdirectory(dirichlet_estimation) if(BUILD_TESTING) + add_subdirectory(dirichlet_estimation_test) add_subdirectory(gamma_estimation_test) + add_subdirectory(watson_estimation_test) endif() add_subdirectory(boot_strap_4d_volume) diff --git a/Anima/math-tools/statistics/dirichlet_estimation/CMakeLists.txt b/Anima/math-tools/statistics/dirichlet_estimation/CMakeLists.txt new file mode 100644 index 000000000..7a93a515f --- /dev/null +++ b/Anima/math-tools/statistics/dirichlet_estimation/CMakeLists.txt @@ -0,0 +1,37 @@ +if(BUILD_TOOLS) + +project(animaDirichletEstimation) + +## ############################################################################# +## List Sources +## ############################################################################# + +list_source_files(${PROJECT_NAME} + ${CMAKE_CURRENT_SOURCE_DIR} + ) + +## ############################################################################# +## add executable +## ############################################################################# + +add_executable(${PROJECT_NAME} + ${${PROJECT_NAME}_CFILES} + ) + + +## ############################################################################# +## Link +## ############################################################################# + +target_link_libraries(${PROJECT_NAME} + AnimaStatisticalDistributions + ${ITKIO_LIBRARIES} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistics/dirichlet_estimation/animaDirichletEstimation.cxx b/Anima/math-tools/statistics/dirichlet_estimation/animaDirichletEstimation.cxx new file mode 100644 index 000000000..a3a42e348 --- /dev/null +++ b/Anima/math-tools/statistics/dirichlet_estimation/animaDirichletEstimation.cxx @@ -0,0 +1,325 @@ +#include +#include +#include +#include + +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg( + "i", "input-files", + "A text file specifying the input image list.", + true, "", "input image list", cmd); + TCLAP::ValueArg outArg( + "o", "output-file", + "A string specifying the name of a file in which a 3D vector image with the concentration parameters of the Dirichlet distribution will be written.", + true, "", "output concentration image", cmd); + TCLAP::ValueArg maskArg( + "m", "mask-files", + "A text file specifying the input mask list.", + false, "", "input masks list", cmd); + TCLAP::ValueArg meanArg( + "", "mean-file", + "A 3D vector image storing the mean of the Dirichlet prior.", + false, "", "output mean image", cmd); + TCLAP::ValueArg varArg( + "", "variance-file", + "A 3D scalar image storing the variance of the Dirichlet prior.", + false, "", "output variance image", cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException &e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + using InputImageType = itk::VectorImage; + using MaskImageType = itk::Image; + using OutputImageType = itk::VectorImage; + using OutputPixelType = OutputImageType::PixelType; + using VarianceImageType = itk::Image; + + // Read input sample + std::ifstream imageIn(inArg.getValue()); + char imageN[2048]; + std::vector inputImages; + while (!imageIn.eof()) + { + imageIn.getline(imageN, 2048); + if (strcmp(imageN, "") == 0) + continue; + inputImages.push_back(anima::readImage(imageN)); + } + imageIn.close(); + + unsigned int nbImages = inputImages.size(); + unsigned int nbComponents = inputImages[0]->GetVectorLength(); + + for (unsigned int i = 1; i < nbImages; ++i) + { + if (inputImages[i]->GetVectorLength() != nbComponents) + { + std::cerr << "The number of components should be the same for all input images." << std::endl; + return EXIT_FAILURE; + } + } + + std::vector maskImages; + if (maskArg.getValue() != "") + { + // Read input masks + std::ifstream masksIn(maskArg.getValue()); + char maskN[2048]; + while (!masksIn.eof()) + { + masksIn.getline(maskN, 2048); + if (strcmp(maskN, "") == 0) + continue; + maskImages.push_back(anima::readImage(maskN)); + } + masksIn.close(); + + if (maskImages.size() != nbImages) + { + std::cerr << "The number of mask images should match the number of input images." << std::endl; + return EXIT_FAILURE; + } + } + + std::cout << "- Number of input images: " << nbImages << std::endl; + std::cout << "- Number of components: " << nbComponents << std::endl; + + using InputImageIteratorType = itk::ImageRegionConstIterator; + using MaskImageIteratorType = itk::ImageRegionConstIterator; + using OutputImageIteratorType = itk::ImageRegionIterator; + using VarianceImageIteratorType = itk::ImageRegionIterator; + + std::vector inItrs(nbImages); + std::vector maskItrs(nbImages); + for (unsigned int i = 0; i < nbImages; ++i) + { + inItrs[i] = InputImageIteratorType(inputImages[i], inputImages[i]->GetLargestPossibleRegion()); + if (maskArg.getValue() != "") + maskItrs[i] = MaskImageIteratorType(maskImages[i], maskImages[i]->GetLargestPossibleRegion()); + } + + // Initialize output image + OutputPixelType outputValue(nbComponents); + outputValue.Fill(0.0); + + OutputImageType::Pointer outputImage = OutputImageType::New(); + outputImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); + outputImage->CopyInformation(inputImages[0]); + outputImage->Allocate(); + outputImage->FillBuffer(outputValue); + + OutputImageType::Pointer meanImage; + OutputImageIteratorType meanItr; + if (meanArg.getValue() != "") + { + meanImage = OutputImageType::New(); + meanImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); + meanImage->CopyInformation(inputImages[0]); + meanImage->Allocate(); + meanImage->FillBuffer(outputValue); + meanItr = OutputImageIteratorType(meanImage, meanImage->GetLargestPossibleRegion()); + } + + VarianceImageType::Pointer varImage; + VarianceImageIteratorType varItr; + if (varArg.getValue() != "") + { + varImage = VarianceImageType::New(); + varImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); + varImage->CopyInformation(inputImages[0]); + varImage->Allocate(); + varImage->FillBuffer(0.0); + varItr = VarianceImageIteratorType(varImage, varImage->GetLargestPossibleRegion()); + } + + OutputImageIteratorType outItr(outputImage, outputImage->GetLargestPossibleRegion()); + std::vector> inputValues, correctedInputValues; + anima::DirichletDistribution dirichletDistribution; + std::vector computedOutputValue; + std::vector usefulValues(nbImages, false); + std::vector usefulComponents(nbComponents, false); + double epsValue = std::sqrt(std::numeric_limits::epsilon()); + + while (!outItr.IsAtEnd()) + { + // Discard background voxels or voxels full of zeros + unsigned int nbUsedImages = 0; + std::fill(usefulValues.begin(), usefulValues.end(), false); + if (maskArg.getValue() != "") + { + for (unsigned int i = 0; i < nbImages; ++i) + { + if (maskItrs[i].Get()) + { + outputValue = inItrs[i].Get(); + + double sumValue = 0.0; + for (unsigned int j = 0; j < nbComponents; ++j) + sumValue += outputValue[j]; + + if (sumValue > epsValue) + { + usefulValues[i] = true; + nbUsedImages++; + } + } + } + } + else + { + for (unsigned int i = 0; i < nbImages; ++i) + { + outputValue = inItrs[i].Get(); + + double sumValue = 0.0; + for (unsigned int j = 0; j < nbComponents; ++j) + sumValue += outputValue[j]; + + if (sumValue > epsValue) + { + usefulValues[i] = true; + nbUsedImages++; + } + } + } + + if (nbUsedImages == 0) + { + for (unsigned int i = 0; i < nbImages; ++i) + { + ++inItrs[i]; + if (maskArg.getValue() != "") + ++maskItrs[i]; + } + ++outItr; + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + continue; + } + + inputValues.resize(nbUsedImages); + unsigned int pos = 0; + for (unsigned int i = 0; i < nbImages; ++i) + { + if (usefulValues[i]) + { + outputValue = inItrs[i].Get(); + inputValues[pos].resize(nbComponents); + for (unsigned int j = 0; j < nbComponents; ++j) + inputValues[pos][j] = outputValue[j]; + pos++; + } + } + + std::fill(usefulComponents.begin(), usefulComponents.end(), false); + unsigned int nbValidComponents = 0; + for (unsigned int j = 0; j < nbComponents; ++j) + { + double meanValue = 0.0; + for (unsigned int i = 0; i < nbUsedImages; ++i) + meanValue += inputValues[i][j]; + meanValue /= static_cast(nbUsedImages); + + double varValue = 0.0; + for (unsigned int i = 0; i < nbUsedImages; ++i) + varValue += (inputValues[i][j] - meanValue) * (inputValues[i][j] - meanValue); + varValue /= (nbUsedImages - 1.0); + + if (varValue > epsValue) + { + usefulComponents[j] = true; + nbValidComponents++; + } + } + + if (nbValidComponents == 0) + { + for (unsigned int i = 0; i < nbImages; ++i) + { + ++inItrs[i]; + if (maskArg.getValue() != "") + ++maskItrs[i]; + } + ++outItr; + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + continue; + } + + correctedInputValues.resize(nbUsedImages); + for (unsigned int i = 0; i < nbUsedImages; ++i) + correctedInputValues[i].resize(nbValidComponents); + pos = 0; + for (unsigned int j = 0; j < nbComponents; ++j) + { + if (usefulComponents[j]) + { + for (unsigned int i = 0; i < nbUsedImages; ++i) + correctedInputValues[i][pos] = inputValues[i][j]; + pos++; + } + } + + dirichletDistribution.Fit(correctedInputValues, "mle"); + computedOutputValue = dirichletDistribution.GetConcentrationParameters(); + + outputValue.Fill(1.0); + pos = 0; + double sumAlpha = 0.0; + for (unsigned int j = 0; j < nbComponents; ++j) + { + if (usefulComponents[j]) + { + outputValue[j] = computedOutputValue[pos]; + pos++; + } + + sumAlpha += outputValue[j]; + } + + outItr.Set(outputValue); + + if (meanImage) + meanItr.Set(outputValue / sumAlpha); + + if (varImage) + varItr.Set(dirichletDistribution.GetVariance()); + + for (unsigned int i = 0; i < nbImages; ++i) + { + ++inItrs[i]; + if (maskArg.getValue() != "") + ++maskItrs[i]; + } + ++outItr; + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + } + + anima::writeImage(outArg.getValue(), outputImage); + + if (meanImage) + anima::writeImage(meanArg.getValue(), meanImage); + if (varImage) + anima::writeImage(varArg.getValue(), varImage); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Anima/math-tools/statistics/dirichlet_estimation_test/CMakeLists.txt b/Anima/math-tools/statistics/dirichlet_estimation_test/CMakeLists.txt new file mode 100644 index 000000000..ef813926e --- /dev/null +++ b/Anima/math-tools/statistics/dirichlet_estimation_test/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TESTING) + +project(animaDirichletEstimationTest) + +## ############################################################################# +## List Sources +## ############################################################################# + +list_source_files(${PROJECT_NAME} + ${CMAKE_CURRENT_SOURCE_DIR} + ) + +## ############################################################################# +## add executable +## ############################################################################# + +add_executable(${PROJECT_NAME} + ${${PROJECT_NAME}_CFILES} + ) + + +## ############################################################################# +## Link +## ############################################################################# + +target_link_libraries(${PROJECT_NAME} + AnimaStatisticalDistributions + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistics/dirichlet_estimation_test/animaDirichletEstimationTest.cxx b/Anima/math-tools/statistics/dirichlet_estimation_test/animaDirichletEstimationTest.cxx new file mode 100644 index 000000000..a6239ae29 --- /dev/null +++ b/Anima/math-tools/statistics/dirichlet_estimation_test/animaDirichletEstimationTest.cxx @@ -0,0 +1,46 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg aArg("a", "a1", "First concentration parameter.", true, 1, "first concentration", cmd); + TCLAP::ValueArg bArg("b", "a2", "Second concentration parameter.", true, 1, "second concentration", cmd); + TCLAP::ValueArg nSampleArg("n", "nsample", "Sample size.", false, 10000, "sample size", cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + std::vector concentrationParameters(2); + concentrationParameters[0] = aArg.getValue(); + concentrationParameters[1] = bArg.getValue(); + + anima::DirichletDistribution dirichletDistribution; + dirichletDistribution.SetConcentrationParameters(concentrationParameters); + + std::mt19937 generator(1234); + + std::vector > sample(nSampleArg.getValue()); + for (unsigned int i = 0;i < nSampleArg.getValue();++i) + sample[i].resize(2); + dirichletDistribution.Random(sample, generator); + + dirichletDistribution.Fit(sample, "mle"); + + concentrationParameters = dirichletDistribution.GetConcentrationParameters(); + std::cout << "Concentration parameters: "; + for (unsigned int i = 0;i < sample[0].size();++i) + std::cout << concentrationParameters[i] << " "; + std::cout << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Anima/math-tools/statistics/gamma_estimation/animaGammaEstimation.cxx b/Anima/math-tools/statistics/gamma_estimation/animaGammaEstimation.cxx index d2740576a..5304734bf 100644 --- a/Anima/math-tools/statistics/gamma_estimation/animaGammaEstimation.cxx +++ b/Anima/math-tools/statistics/gamma_estimation/animaGammaEstimation.cxx @@ -8,31 +8,54 @@ int main(int argc, char **argv) { TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); - - TCLAP::ValueArg inArg("i", "inputfiles", "Input image list in text file", true, "", "input image list", cmd); - TCLAP::ValueArg maskArg("m", "maskfiles", "Input masks list in text file (mask images should contain only zeros or ones)", false, "", "input masks list", cmd); - TCLAP::ValueArg kappaArg("k", "kappafile", "Output kappa image", true, "", "output kappa image", cmd); - TCLAP::ValueArg thetaArg("t", "thetafile", "Output theta image", true, "", "output theta image", cmd); - TCLAP::ValueArg methodArg("", "method", "Estimation method. Choices are: mle, biased-closed-form or unbiased-closed-form", true, "mle", "estimation method", cmd); - + + TCLAP::ValueArg inArg( + "i", "input-files", + "Input image list in text file", + true, "", "input image list", cmd); + TCLAP::ValueArg kappaArg( + "k", "kappa-file", + "Output kappa image", + true, "", "output kappa image", cmd); + TCLAP::ValueArg thetaArg( + "t", "theta-file", + "Output theta image", + true, "", "output theta image", cmd); + TCLAP::ValueArg maskArg( + "m", "mask-files", + "Input masks list in text file (mask images should contain only zeros or ones)", + false, "", "input masks list", cmd); + TCLAP::ValueArg meanArg( + "", "mean-file", + "A 3D scalar image storing the mean of the Gamma prior.", + false, "", "output mean image", cmd); + TCLAP::ValueArg varArg( + "", "variance-file", + "A 3D scalar image storing the variance of the Gamma prior.", + false, "", "output variance image", cmd); + TCLAP::ValueArg methodArg( + "", "method", + "Estimation method. Choices are: mle, biased-closed-form or unbiased-closed-form", + true, "mle", "estimation method", cmd); + try { - cmd.parse(argc,argv); + cmd.parse(argc, argv); } - catch (TCLAP::ArgException& e) + catch (TCLAP::ArgException &e) { std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; return EXIT_FAILURE; } - using InputImageType = itk::Image ; - using MaskImageType = itk::Image ; - using OutputImageType = itk::Image ; + using InputImageType = itk::Image; + using MaskImageType = itk::Image; + using OutputImageType = itk::Image; // Read input sample std::ifstream imageIn(inArg.getValue()); char imageN[2048]; - std::vector inputImages; + std::vector inputImages; while (!imageIn.eof()) { imageIn.getline(imageN, 2048); @@ -42,8 +65,8 @@ int main(int argc, char **argv) } imageIn.close(); unsigned int nbImages = inputImages.size(); - - std::vector maskImages; + + std::vector maskImages; if (maskArg.getValue() != "") { // Read input masks @@ -64,13 +87,13 @@ int main(int argc, char **argv) return EXIT_FAILURE; } } - - using InputImageIteratorType = itk::ImageRegionConstIterator ; - using MaskImageIteratorType = itk::ImageRegionConstIterator ; - using OutputImageIteratorType = itk::ImageRegionIterator ; + + using InputImageIteratorType = itk::ImageRegionConstIterator; + using MaskImageIteratorType = itk::ImageRegionConstIterator; + using OutputImageIteratorType = itk::ImageRegionIterator; std::vector inItrs(nbImages); std::vector maskItrs(nbImages); - for (unsigned int i = 0;i < nbImages;++i) + for (unsigned int i = 0; i < nbImages; ++i) { inItrs[i] = InputImageIteratorType(inputImages[i], inputImages[i]->GetLargestPossibleRegion()); if (maskArg.getValue() != "") @@ -82,7 +105,7 @@ int main(int argc, char **argv) kappaImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); kappaImage->CopyInformation(inputImages[0]); kappaImage->Allocate(); - kappaImage->FillBuffer(0.0); + kappaImage->FillBuffer(0.0); OutputImageIteratorType kappaItr(kappaImage, kappaImage->GetLargestPossibleRegion()); // Initialize theta image @@ -90,50 +113,130 @@ int main(int argc, char **argv) thetaImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); thetaImage->CopyInformation(inputImages[0]); thetaImage->Allocate(); - thetaImage->FillBuffer(0.0); + thetaImage->FillBuffer(0.0); OutputImageIteratorType thetaItr(thetaImage, thetaImage->GetLargestPossibleRegion()); + OutputImageType::Pointer meanImage; + OutputImageIteratorType meanItr; + if (meanArg.getValue() != "") + { + meanImage = OutputImageType::New(); + meanImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); + meanImage->CopyInformation(inputImages[0]); + meanImage->Allocate(); + meanImage->FillBuffer(0.0); + meanItr = OutputImageIteratorType(meanImage, meanImage->GetLargestPossibleRegion()); + } + + OutputImageType::Pointer varImage; + OutputImageIteratorType varItr; + if (varArg.getValue() != "") + { + varImage = OutputImageType::New(); + varImage->SetRegions(inputImages[0]->GetLargestPossibleRegion()); + varImage->CopyInformation(inputImages[0]); + varImage->Allocate(); + varImage->FillBuffer(0.0); + varItr = OutputImageIteratorType(varImage, varImage->GetLargestPossibleRegion()); + } + std::vector inputValues; anima::GammaDistribution gammaDistribution; + double epsValue = std::sqrt(std::numeric_limits::epsilon()); + std::vector usefulValues(nbImages, false); - while (!kappaItr.IsAtEnd()) - { - inputValues.clear(); - + while (!kappaItr.IsAtEnd()) + { unsigned int nbUsedImages = 0; + std::fill(usefulValues.begin(), usefulValues.end(), false); if (maskArg.getValue() != "") { - for (unsigned int i = 0;i < nbImages;++i) + for (unsigned int i = 0; i < nbImages; ++i) { - bool maskValue = maskItrs[i].Get(); - nbUsedImages += maskValue; - if (maskValue) - inputValues.push_back(inItrs[i].Get()); + if (maskItrs[i].Get() && inItrs[i].Get() > epsValue) + { + usefulValues[i] = true; + nbUsedImages++; + } } } else - nbUsedImages = nbImages; - + { + for (unsigned int i = 0; i < nbImages; ++i) + { + if (inItrs[i].Get() > epsValue) + { + usefulValues[i] = true; + nbUsedImages++; + } + } + } + + if (nbUsedImages < 2) + { + for (unsigned int i = 0; i < nbImages; ++i) + { + ++inItrs[i]; + if (maskArg.getValue() != "") + ++maskItrs[i]; + } + ++kappaItr; + ++thetaItr; + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + continue; + } - if (nbUsedImages == 0) - { - for (unsigned int i = 0;i < nbImages;++i) + inputValues.resize(nbUsedImages); + double meanValue = 0.0; + unsigned int pos = 0; + for (unsigned int i = 0; i < nbImages; ++i) + { + if (usefulValues[i]) + { + double tmpValue = inItrs[i].Get(); + inputValues[pos] = tmpValue; + meanValue += tmpValue; + ++pos; + } + } + + meanValue /= static_cast(nbUsedImages); + double varValue = 0.0; + for (unsigned int i = 0; i < nbUsedImages; ++i) + varValue += (inputValues[i] - meanValue) * (inputValues[i] - meanValue); + varValue /= (nbUsedImages - 1.0); + + if (varValue < epsValue) + { + for (unsigned int i = 0; i < nbImages; ++i) { ++inItrs[i]; if (maskArg.getValue() != "") ++maskItrs[i]; } - ++kappaItr; + ++kappaItr; ++thetaItr; - continue; - } + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + continue; + } gammaDistribution.Fit(inputValues, methodArg.getValue()); - + kappaItr.Set(gammaDistribution.GetShapeParameter()); thetaItr.Set(gammaDistribution.GetScaleParameter()); - for (unsigned int i = 0;i < nbImages;++i) + if (meanImage) + meanItr.Set(gammaDistribution.GetMean()); + if (varImage) + varItr.Set(gammaDistribution.GetVariance()); + + for (unsigned int i = 0; i < nbImages; ++i) { ++inItrs[i]; if (maskArg.getValue() != "") @@ -141,10 +244,20 @@ int main(int argc, char **argv) } ++kappaItr; ++thetaItr; - } - - anima::writeImage (kappaArg.getValue(), kappaImage); - anima::writeImage (thetaArg.getValue(), thetaImage); - - return EXIT_SUCCESS; + if (meanImage) + ++meanItr; + if (varImage) + ++varItr; + } + + anima::writeImage(kappaArg.getValue(), kappaImage); + anima::writeImage(thetaArg.getValue(), thetaImage); + + if (meanImage) + anima::writeImage(meanArg.getValue(), meanImage); + + if (varImage) + anima::writeImage(varArg.getValue(), varImage); + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/Anima/math-tools/statistics/watson_estimation_test/CMakeLists.txt b/Anima/math-tools/statistics/watson_estimation_test/CMakeLists.txt new file mode 100644 index 000000000..2fb5ef2cd --- /dev/null +++ b/Anima/math-tools/statistics/watson_estimation_test/CMakeLists.txt @@ -0,0 +1,29 @@ +if(BUILD_TESTING) + + project(animaWatsonEstimationTest) + + # ############################################################################ + # List Sources + # ############################################################################ + + list_source_files(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}) + + # ############################################################################ + # add executable + # ############################################################################ + + add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_CFILES}) + + # ############################################################################ + # Link + # ############################################################################ + + target_link_libraries(${PROJECT_NAME} AnimaStatisticalDistributions) + + # ############################################################################ + # install + # ############################################################################ + + set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistics/watson_estimation_test/animaWatsonEstimationTest.cxx b/Anima/math-tools/statistics/watson_estimation_test/animaWatsonEstimationTest.cxx new file mode 100644 index 000000000..920b24345 --- /dev/null +++ b/Anima/math-tools/statistics/watson_estimation_test/animaWatsonEstimationTest.cxx @@ -0,0 +1,52 @@ +#include +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg kappaArg("k", "kappa", "Concentration parameter.", true, 1, "concentration parameter", cmd); + TCLAP::ValueArg nSampleArg("n", "nsample", "Sample size.", false, 10000, "sample size", cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + double kappa = kappaArg.getValue(); + + anima::WatsonDistribution watsonDistribution; + + using VectorType = itk::Vector; + VectorType meanAxis; + meanAxis.Fill(1.0 / std::sqrt(3.0)); + double normValue = meanAxis.GetNorm(); + meanAxis /= normValue; + watsonDistribution.SetMeanAxis(meanAxis); + watsonDistribution.SetConcentrationParameter(kappa); + + std::mt19937 generator(1234); + + std::vector sample(nSampleArg.getValue()); + watsonDistribution.Random(sample, generator); + + watsonDistribution.Fit(sample, ""); + + std::cout << "----- Mean Axis -----" << std::endl; + std::cout << "- Expected : " << meanAxis << std::endl; + std::cout << "- Estimated: " << watsonDistribution.GetMeanAxis() << std::endl; + + std::cout << std::endl; + + std::cout << "--- Concentration ---" << std::endl; + std::cout << "- Expected : " << kappa << std::endl; + std::cout << "- Estimated: " << watsonDistribution.GetConcentrationParameter() << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt index db89089f2..ac1927ce3 100644 --- a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} + AnimaStatisticalDistributions ) ## ############################################################################# diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx index b6bd6610d..2675c67f5 100644 --- a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx @@ -1,4 +1,5 @@ #include + #include #include @@ -8,127 +9,127 @@ int main(int argc, char **argv) { - TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); - - TCLAP::ValueArg inArg("i","inputfile","Input QMRI images in text file",true,"","input QMRI images",cmd); - TCLAP::ValueArg outArg("o","outputprefix","Output QMR images prefix",true,"","output QMRI prefix",cmd); - TCLAP::ValueArg outLesionMaskArg("O","outputmask","Output lesion mask",false,"","output lesion mask",cmd); - - TCLAP::ValueArg probaArg("p","probafile","Lesions probability image",true,"","lesions probability image",cmd); - TCLAP::ValueArg varImagesArg("v","varfiles","Input QMR variance images in a text file",true,"","input QMR variance images",cmd); - - TCLAP::ValueArg lesionSizeDistArg("l","lesionsizedist","Text file with the cumulative distribution of lesion sizes",true,"","lesion size distribution",cmd); - TCLAP::ValueArg qmriLesionRelationshipsArg("r","lesionqmrirel","Linear relationships between QMRI intensities and distance to lesion border",true,"","qMRI / lesion relationships",cmd); - - TCLAP::ValueArg numSeedsArg("n","numseeds","Number of lesion seeds (default: 5)",false,5,"Number of lesion seeds",cmd); - TCLAP::ValueArg lesionMinSizeArg("m","lesionminsize","Minimal number of voxels in a lesion (default: 5)",false,5,"minimal lesion size in voxels",cmd); - TCLAP::ValueArg minDistArg("d","dist","Minimal distance between seeds (default: 10 mm)",false,10,"minimal distance between seeds",cmd); - - TCLAP::ValueArg diffThresholdArg("t","diffthr","Diffusion grower threshold to binarize grown lesion (default: 1)",false,1,"diffusion grower threshold",cmd); - - TCLAP::ValueArg nbpArg("T","nthreads","Number of cores to run on (default: all cores)",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"Number of cores",cmd); - + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg("i", "inputfile", "Input QMRI images in text file", true, "", "input QMRI images", cmd); + TCLAP::ValueArg outArg("o", "outputprefix", "Output QMR images prefix", true, "", "output QMRI prefix", cmd); + TCLAP::ValueArg outLesionMaskArg("O", "outputmask", "Output lesion mask", false, "", "output lesion mask", cmd); + + TCLAP::ValueArg probaArg("p", "probafile", "Lesions probability image", true, "", "lesions probability image", cmd); + TCLAP::ValueArg varImagesArg("v", "varfiles", "Input QMR variance images in a text file", true, "", "input QMR variance images", cmd); + + TCLAP::ValueArg lesionSizeDistArg("l", "lesionsizedist", "Text file with the cumulative distribution of lesion sizes", true, "", "lesion size distribution", cmd); + TCLAP::ValueArg qmriLesionRelationshipsArg("r", "lesionqmrirel", "Linear relationships between QMRI intensities and distance to lesion border", true, "", "qMRI / lesion relationships", cmd); + + TCLAP::ValueArg numSeedsArg("n", "numseeds", "Number of lesion seeds (default: 5)", false, 5, "Number of lesion seeds", cmd); + TCLAP::ValueArg lesionMinSizeArg("m", "lesionminsize", "Minimal number of voxels in a lesion (default: 5)", false, 5, "minimal lesion size in voxels", cmd); + TCLAP::ValueArg minDistArg("d", "dist", "Minimal distance between seeds (default: 10 mm)", false, 10, "minimal distance between seeds", cmd); + + TCLAP::ValueArg diffThresholdArg("t", "diffthr", "Diffusion grower threshold to binarize grown lesion (default: 1)", false, 1, "diffusion grower threshold", cmd); + + TCLAP::ValueArg nbpArg("T", "nthreads", "Number of cores to run on (default: all cores)", false, itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(), "Number of cores", cmd); + try { - cmd.parse(argc,argv); + cmd.parse(argc, argv); } - catch (TCLAP::ArgException& e) + catch (TCLAP::ArgException &e) { std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; - return(1); + return (1); } - - typedef itk::Image ImageType; - typedef itk::ImageFileReader ImageReaderType; - - typedef anima::QMRISampleCreationImageFilter < itk::Image ,itk::Image > MainFilterType; + + typedef itk::Image ImageType; + typedef itk::ImageFileReader ImageReaderType; + + typedef anima::QMRISampleCreationImageFilter, itk::Image> MainFilterType; MainFilterType::Pointer mainFilter = MainFilterType::New(); - + unsigned int pos = 0; std::ifstream inputFile(inArg.getValue().c_str()); while (!inputFile.eof()) { char tmpStr[2048]; - inputFile.getline(tmpStr,2048); - - if (strcmp(tmpStr,"") != 0) + inputFile.getline(tmpStr, 2048); + + if (strcmp(tmpStr, "") != 0) { ImageReaderType::Pointer tmpReader = ImageReaderType::New(); tmpReader->SetFileName(tmpStr); tmpReader->Update(); - - std::cout << "Loading " << pos+1 << "th qMRI average image " << tmpStr << std::endl; - mainFilter->SetInput(pos,tmpReader->GetOutput()); + + std::cout << "Loading " << pos + 1 << "th qMRI average image " << tmpStr << std::endl; + mainFilter->SetInput(pos, tmpReader->GetOutput()); ++pos; } } - + inputFile.close(); pos = 0; - + std::ifstream varFile(varImagesArg.getValue().c_str()); while (!varFile.eof()) { char tmpStr[2048]; - varFile.getline(tmpStr,2048); - - if (strcmp(tmpStr,"") != 0) + varFile.getline(tmpStr, 2048); + + if (strcmp(tmpStr, "") != 0) { ImageReaderType::Pointer tmpReader = ImageReaderType::New(); tmpReader->SetFileName(tmpStr); tmpReader->Update(); - - std::cout << "Loading " << pos+1 << "th qMRI variance image " << tmpStr << std::endl; + + std::cout << "Loading " << pos + 1 << "th qMRI variance image " << tmpStr << std::endl; mainFilter->AddQMRIVarianceImage(tmpReader->GetOutput()); ++pos; } } - + varFile.close(); - + mainFilter->SetQMRILesionRelationshipsFile(qmriLesionRelationshipsArg.getValue()); mainFilter->ReadLesionSizesDistributions(lesionSizeDistArg.getValue()); - + ImageReaderType::Pointer probaRead = ImageReaderType::New(); probaRead->SetFileName(probaArg.getValue()); probaRead->Update(); - + mainFilter->SetLesionsProbabilityMap(probaRead->GetOutput()); mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); mainFilter->SetLesionDiffusionThreshold(diffThresholdArg.getValue()); mainFilter->SetLesionMinimalSize(lesionMinSizeArg.getValue()); mainFilter->SetMinimalDistanceBetweenLesions(minDistArg.getValue()); mainFilter->SetNumberOfSeeds(numSeedsArg.getValue()); - + mainFilter->Update(); - - typedef itk::ImageFileWriter ImageWriterType; + + typedef itk::ImageFileWriter ImageWriterType; std::string outFileBaseName = outArg.getValue() + "_"; - - for (unsigned int i = 0;i < mainFilter->GetNumberOfOutputs();++i) + + for (unsigned int i = 0; i < mainFilter->GetNumberOfOutputs(); ++i) { std::ostringstream outNum; outNum << i; std::string outFileName = outFileBaseName; outFileName += outNum.str(); outFileName += ".nii.gz"; - + ImageWriterType::Pointer tmpWriter = ImageWriterType::New(); tmpWriter->SetFileName(outFileName); tmpWriter->SetInput(mainFilter->GetOutput(i)); tmpWriter->Update(); } - if (outLesionMaskArg.getValue() != "") + if (outLesionMaskArg.getValue() != "") { - typedef itk::ImageFileWriter MaskImageWriterType; - + typedef itk::ImageFileWriter MaskImageWriterType; + MaskImageWriterType::Pointer maskWriter = MaskImageWriterType::New(); maskWriter->SetInput(mainFilter->GetLesionsOutputMask()); maskWriter->SetFileName(outLesionMaskArg.getValue()); - + maskWriter->Update(); } - - return 0; + + return 0; } diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h index e42233f90..572004246 100644 --- a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h @@ -1,104 +1,105 @@ #pragma once +#include + #include -#include -#include #include +#include +#include namespace anima { -template -class QMRISampleCreationImageFilter -: public itk::ImageToImageFilter -{ -public: - /** Standard class typedefs. */ - typedef QMRISampleCreationImageFilter Self; - typedef TInputImage InputImageType; - typedef TOutputImage OutputImageType; + template + class QMRISampleCreationImageFilter + : public itk::ImageToImageFilter + { + public: + /** Standard class typedefs. */ + typedef QMRISampleCreationImageFilter Self; + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; - typedef itk::ImageToImageFilter Superclass; - typedef itk::SmartPointer Pointer; - typedef itk::SmartPointer ConstPointer; + typedef itk::ImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; - typedef itk::Image MaskImageType; - typedef MaskImageType::Pointer MaskImagePointer; + typedef itk::Image MaskImageType; + typedef MaskImageType::Pointer MaskImagePointer; - /** Method for creation through the object factory. */ - itkNewMacro(Self) + /** Method for creation through the object factory. */ + itkNewMacro(Self); - /** Type macro that defines a name for this class. */ - itkTypeMacro(QMRISampleCreationImageFilter, ImageToImageFilter) + /** Type macro that defines a name for this class. */ + itkTypeMacro(QMRISampleCreationImageFilter, ImageToImageFilter); - /** Smart pointer typedef support. */ - typedef typename TInputImage::Pointer InputImagePointer; - typedef typename TInputImage::ConstPointer InputImageConstPointer; + /** Smart pointer typedef support. */ + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TInputImage::ConstPointer InputImageConstPointer; - itkSetMacro(QMRILesionRelationshipsFile, std::string) - void ReadLesionSizesDistributions(std::string sizeDistributionFile); + itkSetMacro(QMRILesionRelationshipsFile, std::string) void ReadLesionSizesDistributions(std::string sizeDistributionFile); - void AddQMRIVarianceImage(TInputImage *varImage); + void AddQMRIVarianceImage(TInputImage *varImage); - void SetLesionsProbabilityMap (TInputImage *probaImage) - { - m_LesionsProbabilityMap = probaImage; - } + void SetLesionsProbabilityMap(TInputImage *probaImage) + { + m_LesionsProbabilityMap = probaImage; + } - itkSetMacro(LesionDiffusionThreshold, double) - itkSetMacro(LesionMinimalSize, unsigned int) - itkSetMacro(MinimalDistanceBetweenLesions, double) - itkSetMacro(NumberOfSeeds, unsigned int) + itkSetMacro(LesionDiffusionThreshold, double) + itkSetMacro(LesionMinimalSize, unsigned int) + itkSetMacro(MinimalDistanceBetweenLesions, double) + itkSetMacro(NumberOfSeeds, unsigned int) - itkGetMacro(LesionsOutputMask, MaskImageType *) + itkGetMacro(LesionsOutputMask, MaskImageType *) -protected: - QMRISampleCreationImageFilter(); - virtual ~QMRISampleCreationImageFilter() {} + protected : QMRISampleCreationImageFilter(); + virtual ~QMRISampleCreationImageFilter() {} - void GenerateData() ITK_OVERRIDE; + void GenerateData() ITK_OVERRIDE; -private: - ITK_DISALLOW_COPY_AND_ASSIGN(QMRISampleCreationImageFilter); + private: + ITK_DISALLOW_COPY_AND_ASSIGN(QMRISampleCreationImageFilter); - void CheckDataCoherence(); - void InitializeOutputs(); - void ReadQMRILesionRelationships(); + void CheckDataCoherence(); + void InitializeOutputs(); + void ReadQMRILesionRelationships(); - void GenerateQMRIHealthySamples(); - void GenerateAndGrowLesions(); - void UpdateQMRIOnLesions(); - double GetRandomLesionSizeFromDistribution(); + void GenerateQMRIHealthySamples(); + void GenerateAndGrowLesions(); + void UpdateQMRIOnLesions(); + double GetRandomLesionSizeFromDistribution(); - std::vector m_QMRIStdevImages; - InputImagePointer m_LesionsProbabilityMap; + std::vector m_QMRIStdevImages; + InputImagePointer m_LesionsProbabilityMap; - std::vector m_XAxisLesionSizesDistribution; - std::vector m_YAxisLesionSizesDistribution; + std::vector m_XAxisLesionSizesDistribution; + std::vector m_YAxisLesionSizesDistribution; - MaskImagePointer m_LesionsOutputMask; + MaskImagePointer m_LesionsOutputMask; - //! Linear relationship between lesion size and number of iterations in diffusion: y=Ax + B - static const double m_LesionSizeAFactor, m_LesionSizeBFactor; + //! Linear relationship between lesion size and number of iterations in diffusion: y=Ax + B + static const double m_LesionSizeAFactor, m_LesionSizeBFactor; - //! Threshold for diffused lesions - double m_LesionDiffusionThreshold; + //! Threshold for diffused lesions + double m_LesionDiffusionThreshold; - double m_MinimalDistanceBetweenLesions; - unsigned int m_LesionMinimalSize; + double m_MinimalDistanceBetweenLesions; + unsigned int m_LesionMinimalSize; - //! Gaussian relationship between qMRI values inside and outside lesion: I / O ~ N(a,b^2) - std::string m_QMRILesionRelationshipsFile; - std::vector m_QMRILesionMeanRelationships; - vnl_matrix m_QMRILesionCovarianceRelationship; + //! Gaussian relationship between qMRI values inside and outside lesion: I / O ~ N(a,b^2) + std::string m_QMRILesionRelationshipsFile; + std::vector m_QMRILesionMeanRelationships; + vnl_matrix m_QMRILesionCovarianceRelationship; + anima::MultivariateNormalDistribution m_QMRINormalDistribution; - unsigned int m_NumberOfSeeds; - unsigned int m_NumberOfIndividualLesionsKept; + unsigned int m_NumberOfSeeds; + unsigned int m_NumberOfIndividualLesionsKept; - //! Random generator - std::mt19937 m_Generator; -}; + //! Random generator + std::mt19937 m_Generator; + }; } // end of namespace anima diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx index 93273471f..c79ccdb54 100644 --- a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx @@ -3,8 +3,8 @@ #include #include -#include -#include +// #include +// #include #include #include @@ -21,562 +21,548 @@ namespace anima { -template const double QMRISampleCreationImageFilter::m_LesionSizeAFactor = 0.0253; -template const double QMRISampleCreationImageFilter::m_LesionSizeBFactor = - 0.3506; + template + const double QMRISampleCreationImageFilter::m_LesionSizeAFactor = 0.0253; + template + const double QMRISampleCreationImageFilter::m_LesionSizeBFactor = -0.3506; -template -QMRISampleCreationImageFilter -::QMRISampleCreationImageFilter() -{ - m_Generator = std::mt19937 (time(0)); + template + QMRISampleCreationImageFilter::QMRISampleCreationImageFilter() + { + m_Generator = std::mt19937(time(0)); - m_QMRIStdevImages.clear(); - m_XAxisLesionSizesDistribution.clear(); - m_YAxisLesionSizesDistribution.clear(); + m_QMRIStdevImages.clear(); + m_XAxisLesionSizesDistribution.clear(); + m_YAxisLesionSizesDistribution.clear(); - m_LesionDiffusionThreshold = 1; + m_LesionDiffusionThreshold = 1; - m_MinimalDistanceBetweenLesions = 10; - m_LesionMinimalSize = 5; + m_MinimalDistanceBetweenLesions = 10; + m_LesionMinimalSize = 5; - m_QMRILesionMeanRelationships.clear(); + m_QMRILesionMeanRelationships.clear(); - m_NumberOfSeeds = 5; - m_NumberOfIndividualLesionsKept = 0; + m_NumberOfSeeds = 5; + m_NumberOfIndividualLesionsKept = 0; - this->SetNumberOfWorkUnits(itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads()); -} + this->SetNumberOfWorkUnits(itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads()); + } -template -void -QMRISampleCreationImageFilter -::AddQMRIVarianceImage(TInputImage *varImage) -{ - typedef itk::SqrtImageFilter SqrtFilterType; - typename SqrtFilterType::Pointer sqrtFilter = SqrtFilterType::New(); - sqrtFilter->SetInput(varImage); - sqrtFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + template + void + QMRISampleCreationImageFilter::AddQMRIVarianceImage(TInputImage *varImage) + { + typedef itk::SqrtImageFilter SqrtFilterType; + typename SqrtFilterType::Pointer sqrtFilter = SqrtFilterType::New(); + sqrtFilter->SetInput(varImage); + sqrtFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - sqrtFilter->Update(); + sqrtFilter->Update(); - typename TInputImage::Pointer tmpImage = sqrtFilter->GetOutput(); - tmpImage->DisconnectPipeline(); - m_QMRIStdevImages.push_back(tmpImage); -} + typename TInputImage::Pointer tmpImage = sqrtFilter->GetOutput(); + tmpImage->DisconnectPipeline(); + m_QMRIStdevImages.push_back(tmpImage); + } -template -void QMRISampleCreationImageFilter -::InitializeOutputs() -{ - unsigned int numInputs = this->GetNumberOfIndexedInputs(); + template + void QMRISampleCreationImageFilter::InitializeOutputs() + { + unsigned int numInputs = this->GetNumberOfIndexedInputs(); - for (unsigned int i = 0;i < numInputs;++i) - this->SetNthOutput(i, this->MakeOutput(i)); + for (unsigned int i = 0; i < numInputs; ++i) + this->SetNthOutput(i, this->MakeOutput(i)); - this->AllocateOutputs(); -} + this->AllocateOutputs(); + } -template -void -QMRISampleCreationImageFilter -::ReadQMRILesionRelationships() -{ - m_QMRILesionMeanRelationships.resize(this->GetNumberOfIndexedInputs()); - std::ifstream textFile(m_QMRILesionRelationshipsFile.c_str()); + template + void + QMRISampleCreationImageFilter::ReadQMRILesionRelationships() + { + m_QMRILesionMeanRelationships.resize(this->GetNumberOfIndexedInputs()); + std::ifstream textFile(m_QMRILesionRelationshipsFile.c_str()); - if (!textFile.is_open()) - itkExceptionMacro("Could not load qMRI lesion relationship text file..."); + if (!textFile.is_open()) + itkExceptionMacro("Could not load qMRI lesion relationship text file..."); - char tmpStr[8192]; - m_QMRILesionMeanRelationships.resize(this->GetNumberOfIndexedInputs()); + char tmpStr[8192]; + m_QMRILesionMeanRelationships.resize(this->GetNumberOfIndexedInputs()); - // Get mean values - textFile.getline(tmpStr,8192); - while ((strcmp(tmpStr,"") == 0)&&(!textFile.eof())) - textFile.getline(tmpStr,8192); + // Get mean values + textFile.getline(tmpStr, 8192); + while ((strcmp(tmpStr, "") == 0) && (!textFile.eof())) + textFile.getline(tmpStr, 8192); - if (textFile.eof()) - itkExceptionMacro("Malformed qMRI relationship text file"); + if (textFile.eof()) + itkExceptionMacro("Malformed qMRI relationship text file"); - std::string workStr(tmpStr); - workStr.erase(workStr.find_last_not_of(" \n\r\t")+1); + std::string workStr(tmpStr); + workStr.erase(workStr.find_last_not_of(" \n\r\t") + 1); - std::istringstream iss(workStr); - std::string shortStr; - unsigned int pos = 0; - do - { - if (pos >= this->GetNumberOfIndexedInputs()) - itkExceptionMacro("Malformed qMRI relationship text file"); + std::istringstream iss(workStr); + std::string shortStr; + unsigned int pos = 0; + do + { + if (pos >= this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Malformed qMRI relationship text file"); - iss >> shortStr; - m_QMRILesionMeanRelationships[pos] = std::stod(shortStr); - ++pos; - } - while (!iss.eof()); + iss >> shortStr; + m_QMRILesionMeanRelationships[pos] = std::stod(shortStr); + ++pos; + } while (!iss.eof()); - m_QMRILesionCovarianceRelationship.set_size(this->GetNumberOfIndexedInputs(),this->GetNumberOfIndexedInputs()); - pos = 0; - while (!textFile.eof()) - { - textFile.getline(tmpStr,8192); - while ((strcmp(tmpStr,"") == 0)&&(!textFile.eof())) - textFile.getline(tmpStr,8192); + m_QMRILesionCovarianceRelationship.set_size(this->GetNumberOfIndexedInputs(), this->GetNumberOfIndexedInputs()); + pos = 0; + while (!textFile.eof()) + { + textFile.getline(tmpStr, 8192); + while ((strcmp(tmpStr, "") == 0) && (!textFile.eof())) + textFile.getline(tmpStr, 8192); - if (strcmp(tmpStr,"") == 0) - continue; + if (strcmp(tmpStr, "") == 0) + continue; - if (pos >= this->GetNumberOfIndexedInputs()) - itkExceptionMacro("Malformed qMRI relationship text file"); + if (pos >= this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Malformed qMRI relationship text file"); - workStr = tmpStr; - workStr.erase(workStr.find_last_not_of(" \n\r\t")+1); + workStr = tmpStr; + workStr.erase(workStr.find_last_not_of(" \n\r\t") + 1); - std::istringstream issCov(workStr); - unsigned int yPos = 0; - do - { - if (yPos >= this->GetNumberOfIndexedInputs()) + std::istringstream issCov(workStr); + unsigned int yPos = 0; + do + { + if (yPos >= this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Malformed qMRI relationship text file"); + + issCov >> shortStr; + m_QMRILesionCovarianceRelationship(pos, yPos) = std::stod(shortStr); + ++yPos; + } while (!issCov.eof()); + + if (yPos != this->GetNumberOfIndexedInputs()) itkExceptionMacro("Malformed qMRI relationship text file"); - issCov >> shortStr; - m_QMRILesionCovarianceRelationship(pos,yPos) = std::stod(shortStr); - ++yPos; + ++pos; } - while (!issCov.eof()); - if (yPos != this->GetNumberOfIndexedInputs()) + if (pos != this->GetNumberOfIndexedInputs()) itkExceptionMacro("Malformed qMRI relationship text file"); - ++pos; + textFile.close(); + + m_QMRINormalDistribution.SetMeanParameter(m_QMRILesionMeanRelationships); + m_QMRINormalDistribution.SetCovarianceMatrixParameter(m_QMRILesionCovarianceRelationship); } - if (pos != this->GetNumberOfIndexedInputs()) - itkExceptionMacro("Malformed qMRI relationship text file"); + template + void + QMRISampleCreationImageFilter::ReadLesionSizesDistributions(std::string sizeDistributionFile) + { + m_XAxisLesionSizesDistribution.clear(); + m_YAxisLesionSizesDistribution.clear(); - textFile.close(); + std::ifstream textFile(sizeDistributionFile.c_str()); - vnl_matrix eVecs(this->GetNumberOfIndexedInputs(),this->GetNumberOfIndexedInputs()); - vnl_diag_matrix eVals(this->GetNumberOfIndexedInputs()); - itk::SymmetricEigenAnalysis < vnl_matrix , vnl_diag_matrix, vnl_matrix > eigenComputer(this->GetNumberOfIndexedInputs()); - eigenComputer.ComputeEigenValuesAndVectors(m_QMRILesionCovarianceRelationship, eVals, eVecs); + if (!textFile.is_open()) + itkExceptionMacro("Could not load size distribution text file..."); - for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) - eVals[i] = sqrt(eVals[i]); + char tmpStr[8192]; + double currentTotal = 0; + while (!textFile.eof()) + { + textFile.getline(tmpStr, 8192); + if (strcmp(tmpStr, "") == 0) + continue; - anima::RecomposeTensor(eVals,eVecs,m_QMRILesionCovarianceRelationship); -} + double a0, a1; -template -void -QMRISampleCreationImageFilter -::ReadLesionSizesDistributions(std::string sizeDistributionFile) -{ - m_XAxisLesionSizesDistribution.clear(); - m_YAxisLesionSizesDistribution.clear(); + std::stringstream tmpStrStream(tmpStr); + tmpStrStream >> a0 >> a1; - std::ifstream textFile(sizeDistributionFile.c_str()); + m_XAxisLesionSizesDistribution.push_back(a0); + m_YAxisLesionSizesDistribution.push_back(currentTotal + a1); + currentTotal += a1; + } - if (!textFile.is_open()) - itkExceptionMacro("Could not load size distribution text file..."); + if (currentTotal != 1) + { + for (unsigned int i = 0; i < m_YAxisLesionSizesDistribution.size(); ++i) + m_YAxisLesionSizesDistribution[i] /= currentTotal; + } - char tmpStr[8192]; - double currentTotal = 0; - while (!textFile.eof()) + textFile.close(); + } + + template + void + QMRISampleCreationImageFilter::CheckDataCoherence() { - textFile.getline(tmpStr,8192); - if (strcmp(tmpStr,"") == 0) - continue; + if (m_QMRIStdevImages.size() != this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Not the same number of inputs and qMRI variance images..."); - double a0, a1; + if (m_LesionsProbabilityMap.IsNull()) + itkExceptionMacro("No lesion probability mask input..."); - std::stringstream tmpStrStream(tmpStr); - tmpStrStream >> a0 >> a1; + if (this->GetNumberOfIndexedInputs() != m_QMRILesionMeanRelationships.size()) + itkExceptionMacro("Relationships for lesion generation should have the same size as the number of inputs..."); - m_XAxisLesionSizesDistribution.push_back(a0); - m_YAxisLesionSizesDistribution.push_back(currentTotal + a1); - currentTotal += a1; + if (m_XAxisLesionSizesDistribution.size() == 0) + itkExceptionMacro("A distribution of lesion sizes must be given..."); } - if (currentTotal != 1) + template + void + QMRISampleCreationImageFilter::GenerateData() { - for (unsigned int i = 0;i < m_YAxisLesionSizesDistribution.size();++i) - m_YAxisLesionSizesDistribution[i] /= currentTotal; - } + this->ReadQMRILesionRelationships(); + this->CheckDataCoherence(); - textFile.close(); -} - -template -void -QMRISampleCreationImageFilter -::CheckDataCoherence() -{ - if (m_QMRIStdevImages.size() != this->GetNumberOfIndexedInputs()) - itkExceptionMacro("Not the same number of inputs and qMRI variance images..."); + std::cout << "Generating healthy samples from distribution" << std::endl; + // First sample output data from qMRI distribution + this->GenerateQMRIHealthySamples(); - if (m_LesionsProbabilityMap.IsNull()) - itkExceptionMacro("No lesion probability mask input..."); + std::cout << "Generating and growing lesions" << std::endl; + // Then, generate lesion seeds, grow them, and remove those that are too small + this->GenerateAndGrowLesions(); - if (this->GetNumberOfIndexedInputs() != m_QMRILesionMeanRelationships.size()) - itkExceptionMacro("Relationships for lesion generation should have the same size as the number of inputs..."); + std::cout << "Adding lesions to QMRI data" << std::endl; + // Finally, multiply output images by lesion related factor + this->UpdateQMRIOnLesions(); + } - if (m_XAxisLesionSizesDistribution.size() == 0) - itkExceptionMacro("A distribution of lesion sizes must be given..."); -} + template + void + QMRISampleCreationImageFilter::GenerateQMRIHealthySamples() + { + using MultiplyConstantFilterType = itk::MultiplyImageFilter, TInputImage>; + using AddFilterType = itk::AddImageFilter; -template -void -QMRISampleCreationImageFilter -::GenerateData() -{ - this->ReadQMRILesionRelationships(); - this->CheckDataCoherence(); - - std::cout << "Generating healthy samples from distribution" << std::endl; - // First sample output data from qMRI distribution - this->GenerateQMRIHealthySamples(); - - std::cout << "Generating and growing lesions" << std::endl; - // Then, generate lesion seeds, grow them, and remove those that are too small - this->GenerateAndGrowLesions(); - - std::cout << "Adding lesions to QMRI data" << std::endl; - // Finally, multiply output images by lesion related factor - this->UpdateQMRIOnLesions(); -} - -template -void -QMRISampleCreationImageFilter -::GenerateQMRIHealthySamples() -{ - typedef itk::MultiplyImageFilter, TInputImage> MultiplyConstantFilterType; - typedef itk::AddImageFilter AddFilterType; + std::normal_distribution normDistr(0.0, 1.0); - unsigned int numInputs = this->GetNumberOfIndexedInputs(); - for (unsigned int i = 0;i < numInputs;++i) - { - double addOn = anima::SampleFromGaussianDistribution(0.0,0.1,m_Generator); - typename MultiplyConstantFilterType::Pointer multiplyFilter = MultiplyConstantFilterType::New(); - multiplyFilter->SetInput(m_QMRIStdevImages[i]); - multiplyFilter->SetConstant(addOn); - multiplyFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + for (unsigned int i = 0; i < numInputs; ++i) + { + double addOn = normDistr(m_Generator); + typename MultiplyConstantFilterType::Pointer multiplyFilter = MultiplyConstantFilterType::New(); + multiplyFilter->SetInput(m_QMRIStdevImages[i]); + multiplyFilter->SetConstant(addOn); + multiplyFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - multiplyFilter->Update(); + multiplyFilter->Update(); - typename AddFilterType::Pointer addFilter = AddFilterType::New(); - addFilter->SetInput1(this->GetInput(i)); - addFilter->SetInput2(multiplyFilter->GetOutput()); - addFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + typename AddFilterType::Pointer addFilter = AddFilterType::New(); + addFilter->SetInput1(this->GetInput(i)); + addFilter->SetInput2(multiplyFilter->GetOutput()); + addFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - addFilter->Update(); + addFilter->Update(); - typename TOutputImage::Pointer tmpOut = addFilter->GetOutput(); - tmpOut->DisconnectPipeline(); + typename TOutputImage::Pointer tmpOut = addFilter->GetOutput(); + tmpOut->DisconnectPipeline(); - this->SetNthOutput(i,tmpOut); + this->SetNthOutput(i, tmpOut); + } } -} -template -void -QMRISampleCreationImageFilter -::GenerateAndGrowLesions() -{ - //First generate seeds - typedef anima::PickLesionSeedImageFilter SeedPickerFilterType; + template + void + QMRISampleCreationImageFilter::GenerateAndGrowLesions() + { + // First generate seeds + typedef anima::PickLesionSeedImageFilter SeedPickerFilterType; - typename SeedPickerFilterType::Pointer seedLesionsGenerator = SeedPickerFilterType::New(); - seedLesionsGenerator->SetInput(m_LesionsProbabilityMap); - seedLesionsGenerator->SetNumberOfSeeds(m_NumberOfSeeds); - seedLesionsGenerator->SetProximityThreshold(m_MinimalDistanceBetweenLesions); + typename SeedPickerFilterType::Pointer seedLesionsGenerator = SeedPickerFilterType::New(); + seedLesionsGenerator->SetInput(m_LesionsProbabilityMap); + seedLesionsGenerator->SetNumberOfSeeds(m_NumberOfSeeds); + seedLesionsGenerator->SetProximityThreshold(m_MinimalDistanceBetweenLesions); - seedLesionsGenerator->Update(); + seedLesionsGenerator->Update(); - typename MaskImageType::Pointer outputSeedImage = seedLesionsGenerator->GetOutput(); + typename MaskImageType::Pointer outputSeedImage = seedLesionsGenerator->GetOutput(); - m_LesionsOutputMask = MaskImageType::New(); - m_LesionsOutputMask->Initialize(); + m_LesionsOutputMask = MaskImageType::New(); + m_LesionsOutputMask->Initialize(); - m_LesionsOutputMask->SetRegions (outputSeedImage->GetLargestPossibleRegion()); - m_LesionsOutputMask->SetSpacing (outputSeedImage->GetSpacing()); - m_LesionsOutputMask->SetOrigin (outputSeedImage->GetOrigin()); - m_LesionsOutputMask->SetDirection (outputSeedImage->GetDirection()); + m_LesionsOutputMask->SetRegions(outputSeedImage->GetLargestPossibleRegion()); + m_LesionsOutputMask->SetSpacing(outputSeedImage->GetSpacing()); + m_LesionsOutputMask->SetOrigin(outputSeedImage->GetOrigin()); + m_LesionsOutputMask->SetDirection(outputSeedImage->GetDirection()); - m_LesionsOutputMask->Allocate(); + m_LesionsOutputMask->Allocate(); - m_LesionsOutputMask->FillBuffer(0); + m_LesionsOutputMask->FillBuffer(0); - //Then, grow each of them by a random amount - typedef anima::InhomogeneousDiffusionImageFilter DiffusionGrowFilterType; - typedef itk::BinaryThresholdImageFilter GrownLesionThresholdFilterType; - typedef itk::BinaryThresholdImageFilter SeedThresholdFilterType; - typedef itk::AddImageFilter AddLesionFilterType; + // Then, grow each of them by a random amount + typedef anima::InhomogeneousDiffusionImageFilter DiffusionGrowFilterType; + typedef itk::BinaryThresholdImageFilter GrownLesionThresholdFilterType; + typedef itk::BinaryThresholdImageFilter SeedThresholdFilterType; + typedef itk::AddImageFilter AddLesionFilterType; - for (unsigned int i = 1;i <= m_NumberOfSeeds;++i) - { - typename SeedThresholdFilterType::Pointer seedThresholder = SeedThresholdFilterType::New(); - seedThresholder->SetInput(outputSeedImage); - seedThresholder->SetLowerThreshold(i); - seedThresholder->SetUpperThreshold(i); - seedThresholder->SetInsideValue(1); - seedThresholder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - - seedThresholder->Update(); - - typename DiffusionGrowFilterType::Pointer lesionGrower = DiffusionGrowFilterType::New(); - lesionGrower->SetInput(seedThresholder->GetOutput()); - lesionGrower->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - lesionGrower->SetStepLength(0.1); - lesionGrower->SetDiffusionSourceFactor(1); - lesionGrower->SetDiffusionScalarsImage(m_LesionsProbabilityMap); - - double lesionSize = this->GetRandomLesionSizeFromDistribution(); - unsigned int requiredSteps = ceil((lesionSize - m_LesionSizeBFactor) / m_LesionSizeAFactor); - if (requiredSteps < 25) - requiredSteps = 25; - - lesionGrower->SetNumberOfSteps(requiredSteps); - - lesionGrower->Update(); - - typename GrownLesionThresholdFilterType::Pointer grownLesionThrFilter = GrownLesionThresholdFilterType::New(); - grownLesionThrFilter->SetInput(lesionGrower->GetOutput()); - grownLesionThrFilter->SetLowerThreshold(m_LesionDiffusionThreshold); - grownLesionThrFilter->SetInsideValue(1); - grownLesionThrFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - - grownLesionThrFilter->Update(); - - typename AddLesionFilterType::Pointer lesionAdder = AddLesionFilterType::New(); - lesionAdder->SetInput1(m_LesionsOutputMask); - lesionAdder->SetInput2(grownLesionThrFilter->GetOutput()); - lesionAdder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - - lesionAdder->Update(); - m_LesionsOutputMask = lesionAdder->GetOutput(); - m_LesionsOutputMask->DisconnectPipeline(); - } + for (unsigned int i = 1; i <= m_NumberOfSeeds; ++i) + { + typename SeedThresholdFilterType::Pointer seedThresholder = SeedThresholdFilterType::New(); + seedThresholder->SetInput(outputSeedImage); + seedThresholder->SetLowerThreshold(i); + seedThresholder->SetUpperThreshold(i); + seedThresholder->SetInsideValue(1); + seedThresholder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + seedThresholder->Update(); + + typename DiffusionGrowFilterType::Pointer lesionGrower = DiffusionGrowFilterType::New(); + lesionGrower->SetInput(seedThresholder->GetOutput()); + lesionGrower->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + lesionGrower->SetStepLength(0.1); + lesionGrower->SetDiffusionSourceFactor(1); + lesionGrower->SetDiffusionScalarsImage(m_LesionsProbabilityMap); + + double lesionSize = this->GetRandomLesionSizeFromDistribution(); + unsigned int requiredSteps = ceil((lesionSize - m_LesionSizeBFactor) / m_LesionSizeAFactor); + if (requiredSteps < 25) + requiredSteps = 25; + + lesionGrower->SetNumberOfSteps(requiredSteps); + + lesionGrower->Update(); + + typename GrownLesionThresholdFilterType::Pointer grownLesionThrFilter = GrownLesionThresholdFilterType::New(); + grownLesionThrFilter->SetInput(lesionGrower->GetOutput()); + grownLesionThrFilter->SetLowerThreshold(m_LesionDiffusionThreshold); + grownLesionThrFilter->SetInsideValue(1); + grownLesionThrFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + grownLesionThrFilter->Update(); + + typename AddLesionFilterType::Pointer lesionAdder = AddLesionFilterType::New(); + lesionAdder->SetInput1(m_LesionsOutputMask); + lesionAdder->SetInput2(grownLesionThrFilter->GetOutput()); + lesionAdder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + lesionAdder->Update(); + m_LesionsOutputMask = lesionAdder->GetOutput(); + m_LesionsOutputMask->DisconnectPipeline(); + } - // Final thresholding - typedef itk::BinaryThresholdImageFilter MaskThresholdFilterType; - typename MaskThresholdFilterType::Pointer maskThrFilter = MaskThresholdFilterType::New(); - maskThrFilter->SetInput(m_LesionsOutputMask); - maskThrFilter->SetLowerThreshold(1); - maskThrFilter->SetInsideValue(1); - maskThrFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + // Final thresholding + typedef itk::BinaryThresholdImageFilter MaskThresholdFilterType; + typename MaskThresholdFilterType::Pointer maskThrFilter = MaskThresholdFilterType::New(); + maskThrFilter->SetInput(m_LesionsOutputMask); + maskThrFilter->SetLowerThreshold(1); + maskThrFilter->SetInsideValue(1); + maskThrFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - maskThrFilter->Update(); + maskThrFilter->Update(); - typedef itk::ConnectedComponentImageFilter CCFilterType; - CCFilterType::Pointer ccFilter = CCFilterType::New(); - ccFilter->SetInput(maskThrFilter->GetOutput()); - ccFilter->SetFullyConnected(false); + typedef itk::ConnectedComponentImageFilter CCFilterType; + CCFilterType::Pointer ccFilter = CCFilterType::New(); + ccFilter->SetInput(maskThrFilter->GetOutput()); + ccFilter->SetFullyConnected(false); - ccFilter->Update(); + ccFilter->Update(); - itk::ImageRegionIterator ccItr(ccFilter->GetOutput(),ccFilter->GetOutput()->GetLargestPossibleRegion()); - itk::ImageRegionIterator lesionsItr(m_LesionsOutputMask,m_LesionsOutputMask->GetLargestPossibleRegion()); + itk::ImageRegionIterator ccItr(ccFilter->GetOutput(), ccFilter->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator lesionsItr(m_LesionsOutputMask, m_LesionsOutputMask->GetLargestPossibleRegion()); - // Compute component sizes - std::vector componentSizes; - while (!ccItr.IsAtEnd()) - { - unsigned int ccVal = ccItr.Get(); - if (ccVal == 0) + // Compute component sizes + std::vector componentSizes; + while (!ccItr.IsAtEnd()) { + unsigned int ccVal = ccItr.Get(); + if (ccVal == 0) + { + ++ccItr; + continue; + } + + if (ccVal > componentSizes.size()) + { + while (ccVal > componentSizes.size()) + componentSizes.push_back(0); + } + + componentSizes[ccVal - 1]++; ++ccItr; - continue; } - if (ccVal > componentSizes.size()) + unsigned int pos = 1; + for (unsigned int i = 0; i < componentSizes.size(); ++i) { - while (ccVal > componentSizes.size()) - componentSizes.push_back(0); + if (componentSizes[i] > m_LesionMinimalSize) + { + componentSizes[i] = pos; + ++pos; + } + else + componentSizes[i] = 0; } - componentSizes[ccVal-1]++; - ++ccItr; - } - - unsigned int pos = 1; - for (unsigned int i = 0;i < componentSizes.size();++i) - { - if (componentSizes[i] > m_LesionMinimalSize) + ccItr.GoToBegin(); + while (!ccItr.IsAtEnd()) { - componentSizes[i] = pos; - ++pos; - } - else - componentSizes[i] = 0; - } + unsigned int ccVal = ccItr.Get(); + if (ccVal == 0) + { + ++ccItr; + ++lesionsItr; + continue; + } + + lesionsItr.Set(componentSizes[ccVal - 1]); - ccItr.GoToBegin(); - while (!ccItr.IsAtEnd()) - { - unsigned int ccVal = ccItr.Get(); - if (ccVal == 0) - { ++ccItr; ++lesionsItr; - continue; } - lesionsItr.Set(componentSizes[ccVal-1]); - - ++ccItr; - ++lesionsItr; + m_NumberOfIndividualLesionsKept = pos - 1; } - m_NumberOfIndividualLesionsKept = pos - 1; -} - -template -double -QMRISampleCreationImageFilter -::GetRandomLesionSizeFromDistribution() -{ - double yValue = anima::SampleFromUniformDistribution(0.0,1.0,m_Generator); - unsigned int upperIndex = std::lower_bound (m_YAxisLesionSizesDistribution.begin(), m_YAxisLesionSizesDistribution.end(), yValue) - m_YAxisLesionSizesDistribution.begin(); - - if (upperIndex >= m_YAxisLesionSizesDistribution.size()) - return m_XAxisLesionSizesDistribution[m_YAxisLesionSizesDistribution.size() - 1]; - - if (upperIndex == 0) + template + double + QMRISampleCreationImageFilter::GetRandomLesionSizeFromDistribution() { - double factor = m_XAxisLesionSizesDistribution[0] / m_YAxisLesionSizesDistribution[0]; - return yValue * factor; - } - - double aFactor = (m_XAxisLesionSizesDistribution[upperIndex - 1] - m_XAxisLesionSizesDistribution[upperIndex]) / (m_YAxisLesionSizesDistribution[upperIndex - 1] - m_YAxisLesionSizesDistribution[upperIndex]); - double bFactor = (m_XAxisLesionSizesDistribution[upperIndex] * m_YAxisLesionSizesDistribution[upperIndex - 1] - m_XAxisLesionSizesDistribution[upperIndex - 1] * m_YAxisLesionSizesDistribution[upperIndex]) / (m_YAxisLesionSizesDistribution[upperIndex-1] - m_YAxisLesionSizesDistribution[upperIndex]); - - return aFactor * yValue + bFactor; -} - -template -void -QMRISampleCreationImageFilter -::UpdateQMRIOnLesions() -{ - typedef itk::SignedDanielssonDistanceMapImageFilter DistanceFilterType; - typename DistanceFilterType::Pointer distFilter = DistanceFilterType::New(); + std::uniform_real_distribution unifDistr(0.0, 1.0); + double yValue = unifDistr(m_Generator); + unsigned int upperIndex = std::lower_bound(m_YAxisLesionSizesDistribution.begin(), m_YAxisLesionSizesDistribution.end(), yValue) - m_YAxisLesionSizesDistribution.begin(); - distFilter->SetInput(m_LesionsOutputMask); - distFilter->InsideIsPositiveOn(); - distFilter->UseImageSpacingOn(); - distFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + if (upperIndex >= m_YAxisLesionSizesDistribution.size()) + return m_XAxisLesionSizesDistribution[m_YAxisLesionSizesDistribution.size() - 1]; - distFilter->Update(); - - typedef itk::ImageRegionIterator InputImageIteratorType; - InputImageIteratorType distMapItr(distFilter->GetOutput(),m_LesionsOutputMask->GetLargestPossibleRegion()); - typedef itk::ImageRegionIterator MaskIteratorType; - MaskIteratorType lesionsItr(m_LesionsOutputMask,m_LesionsOutputMask->GetLargestPossibleRegion()); - - std::vector maxDists(m_NumberOfIndividualLesionsKept,0); - std::vector numPixels(m_NumberOfIndividualLesionsKept,0); - - // Computing max distances and number of pixels per region - while (!distMapItr.IsAtEnd()) - { - if (lesionsItr.Get() == 0) + if (upperIndex == 0) { - ++lesionsItr; - ++distMapItr; - continue; + double factor = m_XAxisLesionSizesDistribution[0] / m_YAxisLesionSizesDistribution[0]; + return yValue * factor; } - unsigned int lesionNumber = lesionsItr.Get()-1; - if (maxDists[lesionNumber] < distMapItr.Get()) - maxDists[lesionNumber] = distMapItr.Get(); + double aFactor = (m_XAxisLesionSizesDistribution[upperIndex - 1] - m_XAxisLesionSizesDistribution[upperIndex]) / (m_YAxisLesionSizesDistribution[upperIndex - 1] - m_YAxisLesionSizesDistribution[upperIndex]); + double bFactor = (m_XAxisLesionSizesDistribution[upperIndex] * m_YAxisLesionSizesDistribution[upperIndex - 1] - m_XAxisLesionSizesDistribution[upperIndex - 1] * m_YAxisLesionSizesDistribution[upperIndex]) / (m_YAxisLesionSizesDistribution[upperIndex - 1] - m_YAxisLesionSizesDistribution[upperIndex]); - ++numPixels[lesionNumber]; - - ++lesionsItr; - ++distMapItr; + return aFactor * yValue + bFactor; } - //Compute multiplying factors for QMRI lesions - std::vector multiplyingFactors(this->GetNumberOfIndexedInputs(),0); - bool validFactors = false; - while (!validFactors) + template + void + QMRISampleCreationImageFilter::UpdateQMRIOnLesions() { - anima::SampleFromMultivariateGaussianDistribution(m_QMRILesionMeanRelationships,m_QMRILesionCovarianceRelationship, - multiplyingFactors,m_Generator,false); + typedef itk::SignedDanielssonDistanceMapImageFilter DistanceFilterType; + typename DistanceFilterType::Pointer distFilter = DistanceFilterType::New(); - validFactors = true; - for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) - { - if (multiplyingFactors[i] <= 1) - { - validFactors = false; - break; - } - } - } + distFilter->SetInput(m_LesionsOutputMask); + distFilter->InsideIsPositiveOn(); + distFilter->UseImageSpacingOn(); + distFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); - // Now computing mean values on lesions, then change output data - for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) - { - InputImageIteratorType outItr(this->GetOutput(i),this->GetOutput(i)->GetLargestPossibleRegion()); - lesionsItr.GoToBegin(); + distFilter->Update(); + + typedef itk::ImageRegionIterator InputImageIteratorType; + InputImageIteratorType distMapItr(distFilter->GetOutput(), m_LesionsOutputMask->GetLargestPossibleRegion()); + typedef itk::ImageRegionIterator MaskIteratorType; + MaskIteratorType lesionsItr(m_LesionsOutputMask, m_LesionsOutputMask->GetLargestPossibleRegion()); + + std::vector maxDists(m_NumberOfIndividualLesionsKept, 0); + std::vector numPixels(m_NumberOfIndividualLesionsKept, 0); + anima::MultivariateNormalDistribution::SampleType sampleValues(1); - std::vector meanValues(m_NumberOfIndividualLesionsKept,0); - while (!lesionsItr.IsAtEnd()) + // Computing max distances and number of pixels per region + while (!distMapItr.IsAtEnd()) { if (lesionsItr.Get() == 0) { ++lesionsItr; - ++outItr; + ++distMapItr; continue; } - unsigned int lesionNumber = lesionsItr.Get()-1; - meanValues[lesionNumber] += outItr.Get(); + unsigned int lesionNumber = lesionsItr.Get() - 1; + if (maxDists[lesionNumber] < distMapItr.Get()) + maxDists[lesionNumber] = distMapItr.Get(); + + ++numPixels[lesionNumber]; - ++outItr; ++lesionsItr; + ++distMapItr; } - for (unsigned int j = 0;j < m_NumberOfIndividualLesionsKept;++j) + // Compute multiplying factors for QMRI lesions + std::vector multiplyingFactors(this->GetNumberOfIndexedInputs(), 0); + bool validFactors = false; + while (!validFactors) { - if (maxDists[j] == 0) - maxDists[j] = 1; + m_QMRINormalDistribution.Random(sampleValues, m_Generator); + multiplyingFactors = sampleValues[0]; - meanValues[j] /= numPixels[j]; + validFactors = true; + for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) + { + if (multiplyingFactors[i] <= 1) + { + validFactors = false; + break; + } + } } - distMapItr.GoToBegin(); - lesionsItr.GoToBegin(); - outItr.GoToBegin(); - - while (!distMapItr.IsAtEnd()) + // Now computing mean values on lesions, then change output data + for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) { - if (lesionsItr.Get() == 0) + InputImageIteratorType outItr(this->GetOutput(i), this->GetOutput(i)->GetLargestPossibleRegion()); + lesionsItr.GoToBegin(); + + std::vector meanValues(m_NumberOfIndividualLesionsKept, 0); + while (!lesionsItr.IsAtEnd()) { - ++lesionsItr; - ++distMapItr; + if (lesionsItr.Get() == 0) + { + ++lesionsItr; + ++outItr; + continue; + } + + unsigned int lesionNumber = lesionsItr.Get() - 1; + meanValues[lesionNumber] += outItr.Get(); + ++outItr; - continue; + ++lesionsItr; } - unsigned int lesionNumber = lesionsItr.Get()-1; - double expInternalValue = distMapItr.Get() / maxDists[lesionNumber]; - double factor = (1.0 - std::exp(- expInternalValue * expInternalValue * 10.0) / 4.0) * multiplyingFactors[i]; + for (unsigned int j = 0; j < m_NumberOfIndividualLesionsKept; ++j) + { + if (maxDists[j] == 0) + maxDists[j] = 1; - if (factor < 0.9 + 0.1 * m_QMRILesionMeanRelationships[i]) - factor = 0.9 + 0.1 * m_QMRILesionMeanRelationships[i]; + meanValues[j] /= numPixels[j]; + } - double newValue = meanValues[lesionNumber] * factor; - outItr.Set(newValue); + distMapItr.GoToBegin(); + lesionsItr.GoToBegin(); + outItr.GoToBegin(); - ++lesionsItr; - ++distMapItr; - ++outItr; + while (!distMapItr.IsAtEnd()) + { + if (lesionsItr.Get() == 0) + { + ++lesionsItr; + ++distMapItr; + ++outItr; + continue; + } + + unsigned int lesionNumber = lesionsItr.Get() - 1; + double expInternalValue = distMapItr.Get() / maxDists[lesionNumber]; + double factor = (1.0 - std::exp(-expInternalValue * expInternalValue * 10.0) / 4.0) * multiplyingFactors[i]; + + if (factor < 0.9 + 0.1 * m_QMRILesionMeanRelationships[i]) + factor = 0.9 + 0.1 * m_QMRILesionMeanRelationships[i]; + + double newValue = meanValues[lesionNumber] * factor; + outItr.Set(newValue); + + ++lesionsItr; + ++distMapItr; + ++outItr; + } } } -} } // end of namespace anima