From 41dbe1a4b70548ae7144581658cb2408b7830ffe Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Fri, 7 Apr 2023 16:56:45 +0200 Subject: [PATCH 01/17] Fusion diffusion private --- Anima/.gitattributes | 15 + Anima/.gitignore | 10 + Anima/README.md | 6 + Anima/diffusion/CMakeLists.txt | 2 + Anima/diffusion/dti/CMakeLists.txt | 6 + .../CMakeLists.txt | 38 + ...groundNoiseVarianceEstimationImageFilter.h | 119 +++ ...oundNoiseVarianceEstimationImageFilter.hxx | 313 +++++++ .../animaBackgroundNoiseVarianceEstimator.cxx | 124 +++ .../{ => dti}/dti_estimator/CMakeLists.txt | 0 .../animaDTIEstimationImageFilter.h | 0 .../animaDTIEstimationImageFilter.hxx | 0 .../dti_estimator/animaDTIEstimator.cxx | 0 .../CMakeLists.txt | 40 + .../animaDTINonCentralChiCostFunction.cxx | 88 ++ .../animaDTINonCentralChiCostFunction.h | 72 ++ ...imaDTINonCentralChiEstimationImageFilter.h | 137 +++ ...aDTINonCentralChiEstimationImageFilter.hxx | 522 ++++++++++++ .../animaDTINonCentralChiEstimator.cxx | 182 ++++ .../dti_scalar_maps/CMakeLists.txt | 0 .../dti_scalar_maps/animaDTIScalarMaps.cxx | 0 .../animaDTIScalarMapsImageFilter.h | 0 .../animaDTIScalarMapsImageFilter.hxx | 0 .../dwi_simulator_from_dti/CMakeLists.txt | 0 .../animaDWISimulatorFromDTI.cxx | 0 .../animaDWISimulatorFromDTIImageFilter.h | 0 .../animaDWISimulatorFromDTIImageFilter.hxx | 0 .../flip_tensors/CMakeLists.txt | 0 .../flip_tensors/animaFlipTensorImageFilter.h | 0 .../animaFlipTensorImageFilter.hxx | 0 .../flip_tensors/animaFlipTensors.cxx | 0 Anima/diffusion/dti_tools/CMakeLists.txt | 3 - .../diffusion/fibers_analysis/CMakeLists.txt | 2 + .../CMakeLists.txt | 45 + .../animaFiberPropertyExtractAndRescale.cxx | 118 +++ .../fibers_a_contrario/CMakeLists.txt | 47 ++ .../animaFibersAContrario.cxx | 216 +++++ Anima/diffusion/mcm/CMakeLists.txt | 5 +- Anima/diffusion/mcm/animaDDICompartment.cxx | 325 +++++++ Anima/diffusion/mcm/animaDDICompartment.h | 77 ++ .../mcm/animaMultiCompartmentModelCreator.cxx | 28 +- .../mcm/animaMultiCompartmentModelCreator.h | 2 +- .../mcm/animaNonCentralChiMCMCost.cxx | 89 ++ .../diffusion/mcm/animaNonCentralChiMCMCost.h | 56 ++ .../animaMCMEstimatorImageFilter.h | 4 +- .../animaMCMEstimatorImageFilter.hxx | 91 +- .../mcm_estimator/low_memory/CMakeLists.txt | 43 + .../low_memory/animaLowMemMCMEstimator.cxx | 165 ++++ .../animaLowMemMCMEstimatorBridge.cxx | 384 +++++++++ .../animaLowMemMCMEstimatorBridge.h | 180 ++++ .../mcm_model_averaging/CMakeLists.txt | 41 + .../animaMCMImageSimplifier.h | 74 ++ .../animaMCMImageSimplifier.hxx | 161 ++++ .../animaMCMModelAveraging.cxx | 206 +++++ .../animaMCMModelAveragingImageFilter.h | 118 +++ .../animaMCMModelAveragingImageFilter.hxx | 793 ++++++++++++++++++ Anima/diffusion/mcm_tools/CMakeLists.txt | 6 + .../dwi_simulation_from_mcm/CMakeLists.txt | 39 + .../animaDWISimulationFromMCM.cxx | 147 ++++ .../CMakeLists.txt | 38 + .../animaGenerateIsoradiusDDISurface.cxx | 81 ++ .../get_scalar_map_from_ddi/CMakeLists.txt | 38 + .../animaGetScalarMapFromDDI.cxx | 240 ++++++ .../animaMCMAverageImagesImageFilter.h | 5 +- .../animaMCMAverageImagesImageFilter.hxx | 33 +- .../mcm_merge_block_images/CMakeLists.txt | 39 + .../animaMCMMergeBlockImages.cxx | 143 ++++ .../mt_estimation_validation/CMakeLists.txt | 38 + .../animaMTEstimationValidation.cxx | 251 ++++++ .../mcm_tools/test_averaging/CMakeLists.txt | 4 + .../create_grid_from_pixels/CMakeLists.txt | 39 + .../animaCreateGridFromPixels.cxx | 153 ++++ .../ddi_averaging_on_a_grid/CMakeLists.txt | 38 + .../animaDDIAveragingOnAGrid.cxx | 152 ++++ .../CMakeLists.txt | 38 + .../animaDDITestAveragingOnRealValue.cxx | 94 +++ ...DDITestAveragingOnRealValueImageFilter.cxx | 112 +++ ...maDDITestAveragingOnRealValueImageFilter.h | 67 ++ .../random_ddi_generator/CMakeLists.txt | 35 + .../animaRandomDDIGenerator.cxx | 91 ++ Anima/diffusion/tractography/CMakeLists.txt | 2 + ...CMProbabilisticTractographyImageFilter.cxx | 344 ++++++++ ...aMCMProbabilisticTractographyImageFilter.h | 78 ++ .../animaMCMTractographyImageFilter.cxx | 159 ++++ .../animaMCMTractographyImageFilter.h | 62 ++ .../CMakeLists.txt | 46 + .../animaMCMProbabilisticTractography.cxx | 178 ++++ .../mcm_tractography/CMakeLists.txt | 44 + .../mcm_tractography/animaMCMTractography.cxx | 109 +++ 89 files changed, 7834 insertions(+), 56 deletions(-) create mode 100644 Anima/.gitattributes create mode 100644 Anima/.gitignore create mode 100644 Anima/README.md create mode 100644 Anima/diffusion/dti/CMakeLists.txt create mode 100644 Anima/diffusion/dti/background_noise_variance_estimator/CMakeLists.txt create mode 100644 Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.h create mode 100644 Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.hxx create mode 100644 Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimator.cxx rename Anima/diffusion/{ => dti}/dti_estimator/CMakeLists.txt (100%) rename Anima/diffusion/{ => dti}/dti_estimator/animaDTIEstimationImageFilter.h (100%) rename Anima/diffusion/{ => dti}/dti_estimator/animaDTIEstimationImageFilter.hxx (100%) rename Anima/diffusion/{ => dti}/dti_estimator/animaDTIEstimator.cxx (100%) create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/CMakeLists.txt create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.cxx create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.h create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.h create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.hxx create mode 100644 Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimator.cxx rename Anima/diffusion/{dti_tools => dti}/dti_scalar_maps/CMakeLists.txt (100%) rename Anima/diffusion/{dti_tools => dti}/dti_scalar_maps/animaDTIScalarMaps.cxx (100%) rename Anima/diffusion/{dti_tools => dti}/dti_scalar_maps/animaDTIScalarMapsImageFilter.h (100%) rename Anima/diffusion/{dti_tools => dti}/dti_scalar_maps/animaDTIScalarMapsImageFilter.hxx (100%) rename Anima/diffusion/{dti_tools => dti}/dwi_simulator_from_dti/CMakeLists.txt (100%) rename Anima/diffusion/{dti_tools => dti}/dwi_simulator_from_dti/animaDWISimulatorFromDTI.cxx (100%) rename Anima/diffusion/{dti_tools => dti}/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.h (100%) rename Anima/diffusion/{dti_tools => dti}/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.hxx (100%) rename Anima/diffusion/{dti_tools => dti}/flip_tensors/CMakeLists.txt (100%) rename Anima/diffusion/{dti_tools => dti}/flip_tensors/animaFlipTensorImageFilter.h (100%) rename Anima/diffusion/{dti_tools => dti}/flip_tensors/animaFlipTensorImageFilter.hxx (100%) rename Anima/diffusion/{dti_tools => dti}/flip_tensors/animaFlipTensors.cxx (100%) delete mode 100644 Anima/diffusion/dti_tools/CMakeLists.txt create mode 100644 Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/CMakeLists.txt create mode 100644 Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/animaFiberPropertyExtractAndRescale.cxx create mode 100644 Anima/diffusion/fibers_analysis/fibers_a_contrario/CMakeLists.txt create mode 100644 Anima/diffusion/fibers_analysis/fibers_a_contrario/animaFibersAContrario.cxx create mode 100644 Anima/diffusion/mcm/animaDDICompartment.cxx create mode 100644 Anima/diffusion/mcm/animaDDICompartment.h create mode 100644 Anima/diffusion/mcm/animaNonCentralChiMCMCost.cxx create mode 100644 Anima/diffusion/mcm/animaNonCentralChiMCMCost.h create mode 100644 Anima/diffusion/mcm_estimator/low_memory/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimator.cxx create mode 100644 Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.cxx create mode 100644 Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.h create mode 100644 Anima/diffusion/mcm_model_averaging/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.h create mode 100644 Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.hxx create mode 100644 Anima/diffusion/mcm_model_averaging/animaMCMModelAveraging.cxx create mode 100644 Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.h create mode 100644 Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.hxx create mode 100644 Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/animaDWISimulationFromMCM.cxx create mode 100644 Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/animaGenerateIsoradiusDDISurface.cxx create mode 100644 Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/animaGetScalarMapFromDDI.cxx create mode 100644 Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx create mode 100644 Anima/diffusion/mcm_tools/mt_estimation_validation/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/mt_estimation_validation/animaMTEstimationValidation.cxx create mode 100644 Anima/diffusion/mcm_tools/test_averaging/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx create mode 100644 Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.h create mode 100644 Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/CMakeLists.txt create mode 100644 Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/animaRandomDDIGenerator.cxx create mode 100644 Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx create mode 100644 Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.h create mode 100644 Anima/diffusion/tractography/animaMCMTractographyImageFilter.cxx create mode 100644 Anima/diffusion/tractography/animaMCMTractographyImageFilter.h create mode 100644 Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt create mode 100644 Anima/diffusion/tractography/mcm_probabilistic_tractography/animaMCMProbabilisticTractography.cxx create mode 100644 Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt create mode 100644 Anima/diffusion/tractography/mcm_tractography/animaMCMTractography.cxx diff --git a/Anima/.gitattributes b/Anima/.gitattributes new file mode 100644 index 000000000..2991839e6 --- /dev/null +++ b/Anima/.gitattributes @@ -0,0 +1,15 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text eol=lf +*.h text eol=lf +*.cxx text eol=lf +*.hxx text eol=lf +*.cpp text eol=lf +*.txt text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary diff --git a/Anima/.gitignore b/Anima/.gitignore new file mode 100644 index 000000000..d05fc2691 --- /dev/null +++ b/Anima/.gitignore @@ -0,0 +1,10 @@ +*~ +*.swp +*.user +*.orig +*.directory +*.DS_Store +*.aps +*.autosave +CMakeLists.txt.user.* +CMakeFiles diff --git a/Anima/README.md b/Anima/README.md new file mode 100644 index 000000000..aa34ddc66 --- /dev/null +++ b/Anima/README.md @@ -0,0 +1,6 @@ +ANIMA: Source Code Repository +----- + +ANIMA has been created as the new place to hold the softwares from Empenn. Mainly based on ITK and VTK, it has been designed to hold C++ source code in a modular structure. People in the team are encouraged to contribute their algorithms here so that others may benefit from it easily. + +Refer to the [wiki](https://github.com/Inria-Visages/Anima/wiki) for more information diff --git a/Anima/diffusion/CMakeLists.txt b/Anima/diffusion/CMakeLists.txt index 3fe57ce6e..3ae0803ec 100644 --- a/Anima/diffusion/CMakeLists.txt +++ b/Anima/diffusion/CMakeLists.txt @@ -8,6 +8,8 @@ add_subdirectory(dti_estimator) add_subdirectory(dti_tools) add_subdirectory(mcm) add_subdirectory(mcm_estimator) +add_subdirectory(mcm_estimator/low_memory) +add_subdirectory(mcm_model_averaging) add_subdirectory(mcm_tools) if(USE_VTK AND VTK_FOUND) diff --git a/Anima/diffusion/dti/CMakeLists.txt b/Anima/diffusion/dti/CMakeLists.txt new file mode 100644 index 000000000..47acb03fa --- /dev/null +++ b/Anima/diffusion/dti/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(dti_estimator) +add_subdirectory(dti_scalar_maps) +add_subdirectory(dwi_simulator_from_dti) +add_subdirectory(flip_tensors) +add_subdirectory(background_noise_variance_estimator) +add_subdirectory(dti_non_central_chi_estimator) diff --git a/Anima/diffusion/dti/background_noise_variance_estimator/CMakeLists.txt b/Anima/diffusion/dti/background_noise_variance_estimator/CMakeLists.txt new file mode 100644 index 000000000..51ba8e7a4 --- /dev/null +++ b/Anima/diffusion/dti/background_noise_variance_estimator/CMakeLists.txt @@ -0,0 +1,38 @@ +if(BUILD_TOOLS) + +project(animaBackgroundNoiseVarianceEstimator) + +## ############################################################################# +## 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} + AnimaStatisticalTests + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.h b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.h new file mode 100644 index 000000000..d1e3e0adb --- /dev/null +++ b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace anima +{ +template +class BackgroundNoiseVarianceEstimationImageFilter : + public anima::NumberedThreadImageToImageFilter < TInputImage, itk::Image > +{ +public: + /** Standard class typedefs. */ + typedef BackgroundNoiseVarianceEstimationImageFilter Self; + typedef itk::Image TOutputImage; + typedef itk::VectorImage VectorImageType; + typedef VectorImageType::Pointer VectorImagePointer; + + 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(BackgroundNoiseVarianceEstimationImageFilter, 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 InputImageType::IndexType InputImageIndexType; + typedef typename InputImageType::PixelType InputImagePixelType; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + //! Actually computes partial variances for this region + void ComputePartialVariance(const OutputImageRegionType ®ion); + + //! Actually updates output for this region + void PartialUpdateOutput(const OutputImageRegionType ®ion); + + itkSetMacro(PValueThreshold, double) + itkGetMacro(OutputVariance, double) + + itkSetMacro(NumberOfCoils, unsigned int) + + void SetTheoreticalSnr(std::vector theoreticalSnr ){m_TheoreticalSnr = theoreticalSnr;} + void SetBValuesList(std::vector bValuesList ){m_BValuesList = bValuesList;} + + itkSetMacro(QuantileInitialization, double) + + itkSetMacro(EstimatedB0Image, InputImagePointer) + itkSetMacro(DTIImage, VectorImagePointer) + + void AddGradientDirection(unsigned int i, std::vector &grad); + +protected: + BackgroundNoiseVarianceEstimationImageFilter() + : Superclass() + { + m_NumberOfCoils = 1; + m_PValueThreshold = 0.05; + m_OutputVariance = 0; + m_SlopeInterceptDesignPart = 0; + m_QuantileInitialization = 0.5; + + m_EstimatedB0Image = NULL; + m_DTIImage = NULL; + } + + virtual ~BackgroundNoiseVarianceEstimationImageFilter() {} + + //Redefine virtual functions + void GenerateData() ITK_OVERRIDE; + + unsigned int ComputeInitialOutputFromDTI(); + unsigned int UpdateOutputFromPValues(); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(BackgroundNoiseVarianceEstimationImageFilter); + + std::vector m_PartialVariances; + std::vector m_PartialNumberOfCoils; + + itk::Image::Pointer m_WorkPValImage; + InputImagePointer m_EstimatedB0Image; + VectorImagePointer m_DTIImage; + + std::vector m_BValuesList; + std::vector< std::vector > m_GradientDirections; + vnl_matrix m_DesignMatrix; + double m_SlopeInterceptDesignPart; + double m_QuantileInitialization; + + double m_OutputVariance; + unsigned int m_TotalNumberOfCoils; + unsigned int m_NumPixels; + unsigned int m_NumberOfCoils; + std::vector m_TheoreticalSnr; + double m_PValueThreshold; + + static const unsigned int m_NumberOfComponents = 6; +}; + +} // end of namespace anima + +#include "animaBackgroundNoiseVarianceEstimationImageFilter.hxx" diff --git a/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.hxx b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.hxx new file mode 100644 index 000000000..774d4039e --- /dev/null +++ b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimationImageFilter.hxx @@ -0,0 +1,313 @@ +#pragma once + +#include "animaBackgroundNoiseVarianceEstimationImageFilter.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace anima +{ + +template< typename TInputImage > +void +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::AddGradientDirection(unsigned int i, std::vector &grad) +{ + if (i == m_GradientDirections.size()) + m_GradientDirections.push_back(grad); + else if (i > m_GradientDirections.size()) + std::cerr << "Trying to add a direction not contiguous... Add directions contiguously (0,1,2,3,...)..." << std::endl; + else + m_GradientDirections[i] = grad; +} + +template< typename TInputImage > +void +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::GenerateData() +{ + if (m_BValuesList.size() != this->GetNumberOfIndexedInputs()) + { + std::string error("There should be the same number of input images and input b-values... "); + error += m_BValuesList.size(); + error += " "; + error += this->GetNumberOfIndexedInputs(); + throw itk::ExceptionObject(__FILE__, __LINE__,error,ITK_LOCATION); + } + + if (!m_EstimatedB0Image) + throw itk::ExceptionObject(__FILE__, __LINE__,"Missing estimated B0 image...",ITK_LOCATION); + + if (!m_DTIImage) + throw itk::ExceptionObject(__FILE__, __LINE__,"Missing estimated DTI image...",ITK_LOCATION); + + this->AllocateOutputs(); + this->BeforeThreadedGenerateData(); + + m_NumPixels = this->ComputeInitialOutputFromDTI(); + + unsigned int numIter = 0; + m_PartialVariances.resize(this->GetNumberOfWorkUnits()); + + unsigned int numPixelsOld; + bool stopLoop = false; + + m_WorkPValImage = itk::Image::New(); + m_WorkPValImage->Initialize(); + m_WorkPValImage->SetOrigin(this->GetOutput()->GetOrigin()); + m_WorkPValImage->SetDirection(this->GetOutput()->GetDirection()); + m_WorkPValImage->SetSpacing(this->GetOutput()->GetSpacing()); + m_WorkPValImage->SetRegions(this->GetOutput()->GetLargestPossibleRegion()); + m_WorkPValImage->Allocate(); + + this->GetMultiThreader()->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + while (!stopLoop) + { + ++numIter; + + std::fill(m_PartialVariances.begin(),m_PartialVariances.end(),0); + numPixelsOld = m_NumPixels; + + // Parallelize by calling ComputePartialVariance + this->GetMultiThreader()->template ParallelizeImageRegion ( + this->GetOutput()->GetRequestedRegion(), + [this](const OutputImageRegionType & outputRegionForThread) + { this->ComputePartialVariance(outputRegionForThread); }, this); + + m_OutputVariance = 0; + for (int i = 0;i < this->GetNumberOfWorkUnits();++i) + m_OutputVariance += m_PartialVariances[i]; + + m_OutputVariance /= (m_NumPixels * m_NumberOfCoils); + + // Parallelize by calling ComputePartialVariance + this->GetMultiThreader()->template ParallelizeImageRegion ( + this->GetOutput()->GetRequestedRegion(), + [this](const OutputImageRegionType & outputRegionForThread) + { this->PartialUpdateOutput(outputRegionForThread); }, this); + + m_NumPixels += this->UpdateOutputFromPValues(); + + if (m_NumPixels == numPixelsOld) + stopLoop = true; + } +} + +template< typename TInputImage > +unsigned int +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::ComputeInitialOutputFromDTI() +{ + // Design matrix computation + + m_DesignMatrix.set_size(m_GradientDirections.size(),m_NumberOfComponents+1); + + for (unsigned int i = 0;i < m_GradientDirections.size();++i) + { + m_DesignMatrix(i,0) = 1; + + unsigned int pos = 1; + for (unsigned int j = 0;j < 3;++j) + for (unsigned int k = 0;k <= j;++k) + { + if (j != k) + m_DesignMatrix(i,pos) = - 2 * m_BValuesList[i] * m_GradientDirections[i][j] * m_GradientDirections[i][k]; + else + m_DesignMatrix(i,pos) = - m_BValuesList[i] * m_GradientDirections[i][j] * m_GradientDirections[i][j]; + + ++pos; + } + } + + vnl_matrix pseudoSolveMatrix = vnl_matrix_inverse (m_DesignMatrix.transpose() * m_DesignMatrix).as_matrix(); + m_SlopeInterceptDesignPart = pseudoSolveMatrix(0,0); + + typedef itk::ImageRegionIterator MaskRegionIteratorType; + typedef itk::ImageRegionConstIterator InputRegionIteratorType; + + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + std::vector inIterators(numInputs); + for (unsigned int i = 0;i < numInputs;++i) + inIterators[i] = InputRegionIteratorType(this->GetInput(i),this->GetOutput()->GetLargestPossibleRegion()); + + MaskRegionIteratorType maskItr(this->GetOutput(), this->GetOutput()->GetLargestPossibleRegion()); + std::vector nonB0Values(numInputs-1); + +// unsigned int indexCompare = (numInputs-1)/2; +// if (!m_MedianInitialization) + unsigned int indexCompare = (unsigned int)floor((numInputs-1)*m_QuantileInitialization); + + unsigned int numOutPixels = 0; + while (!maskItr.IsAtEnd()) + { + for (unsigned int i = 0;i < numInputs-1;++i) + nonB0Values[i] = inIterators[i+1].Get(); + + std::partial_sort(nonB0Values.begin(),nonB0Values.begin()+indexCompare+1,nonB0Values.end()); + + if (inIterators[0].Get() <= nonB0Values[indexCompare]) + { + ++numOutPixels; + maskItr.Set(1); + } + else + maskItr.Set(0); + + ++maskItr; + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + } + + return numOutPixels; +} + +template< typename TInputImage > +void +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::ComputePartialVariance(const OutputImageRegionType ®ion) +{ + typedef itk::ImageRegionConstIterator MaskRegionConstIteratorType; + typedef itk::ImageRegionConstIterator RegionConstIteratorType; + + MaskRegionConstIteratorType maskItr(this->GetOutput(),region); + + unsigned int numInputs = this->GetNumberOfIndexedInputs() - 1; + std::vector inIterators(numInputs); + for (unsigned int i = 0;i < numInputs;++i) + inIterators[i] = RegionConstIteratorType(this->GetInput(i+1),region); + + unsigned int threadId = this->GetSafeThreadId(); + m_PartialVariances[threadId] = 0; + + while(!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++maskItr; + continue; + } + + for (unsigned int i = 0;i < numInputs;++i) + { + double tmpVal = inIterators[i].Get(); + m_PartialVariances[threadId] += tmpVal * tmpVal; + } + + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++maskItr; + } + + m_PartialVariances[threadId] /= (2.0 * numInputs); + + this->SafeReleaseThreadId(threadId); +} + +template< typename TInputImage > +void +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::PartialUpdateOutput(const OutputImageRegionType ®ion) +{ + typedef itk::ImageRegionConstIterator MaskRegionIteratorType; + typedef itk::ImageRegionIterator < itk::Image > PValRegionIteratorType; + typedef itk::ImageRegionConstIterator RegionConstIteratorType; + + MaskRegionIteratorType maskItr(this->GetOutput(),region); + PValRegionIteratorType pvItr(m_WorkPValImage,region); + + unsigned int numInputs = this->GetNumberOfIndexedInputs() - 1; + std::vector inIterators(numInputs); + for (unsigned int i = 0;i < numInputs;++i) + inIterators[i] = RegionConstIteratorType(this->GetInput(i+1),region); + + boost::math::fisher_f_distribution<> f_dist(2.0 * numInputs * m_NumberOfCoils, 2.0 * numInputs * m_NumPixels * m_NumberOfCoils); + + while(!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 1) + { + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++maskItr; + ++pvItr; + continue; + } + + double statistic = 0; + for (unsigned int i = 0;i < numInputs;++i) + { + double tmpVal = inIterators[i].Get(); + statistic += tmpVal * tmpVal; + } + + statistic /= (2.0 * m_NumberOfCoils * numInputs * m_OutputVariance); + + pvItr.Set(1.0 - boost::math::cdf(f_dist, statistic)); + + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++maskItr; + ++pvItr; + } +} + +template< typename TInputImage > +unsigned int +BackgroundNoiseVarianceEstimationImageFilter< TInputImage > +::UpdateOutputFromPValues() +{ + typedef itk::ImageRegionIterator MaskRegionIteratorType; + typedef itk::ImageRegionConstIterator < itk::Image > PValRegionIteratorType; + + MaskRegionIteratorType maskItr(this->GetOutput(),this->GetOutput()->GetLargestPossibleRegion()); + PValRegionIteratorType pvItr(m_WorkPValImage,this->GetOutput()->GetLargestPossibleRegion()); + + std::vector pvalues; + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + pvalues.push_back(pvItr.Get()); + + ++maskItr; + ++pvItr; + } + + anima::BHCorrection(pvalues, m_PValueThreshold); + + unsigned int numPtsAdded = 0; + maskItr.GoToBegin(); + + unsigned int pos = 0; + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + if (pvalues[pos] != 0) + { + maskItr.Set(1); + ++numPtsAdded; + } + + ++pos; + } + + ++maskItr; + } + + return numPtsAdded; +} + +} // end namespace anima diff --git a/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimator.cxx b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimator.cxx new file mode 100644 index 000000000..da1713d5b --- /dev/null +++ b/Anima/diffusion/dti/background_noise_variance_estimator/animaBackgroundNoiseVarianceEstimator.cxx @@ -0,0 +1,124 @@ +#include + +#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","inputdwi","DWI volume",true,"","DWI volume",cmd); + TCLAP::ValueArg resArg("o","output","Result mask image",true,"","result mask image",cmd); + + TCLAP::ValueArg inB0Arg("","b0","Estimated B0 volume",true,"","estimated B0 volume",cmd); + TCLAP::ValueArg inDTIArg("d","dti-input","Input DTI image",true,"","input DTI image",cmd); + + TCLAP::ValueArg gradsArg("g","grad","Input gradients",true,"","Input gradients",cmd); + TCLAP::ValueArg bvalArg("b","bval","Input b-values",true,"","Input b-values",cmd); + + TCLAP::ValueArg quantileInitArg("q","quantile-init","Initialize masking with q quantile (default: 0.5)",false,0.5,"Quantile threshold",cmd); + + TCLAP::ValueArg pvThrArg("","pv","P-value threshold for mask update (default: 0.05)",false,0.05,"P-value threshold",cmd); + + TCLAP::ValueArg numCoilsArg("c","ncoils","Number of coils (default : 1)",false,1,"Number of coils",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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + typedef itk::Image InputImageType; + typedef anima::BackgroundNoiseVarianceEstimationImageFilter FilterType; + typedef FilterType::VectorImageType VectorImageType; + typedef FilterType::OutputImageType MaskImageType; + + typedef itk::ImageFileReader InputImageReaderType; + typedef itk::ImageFileReader VectorImageReaderType; + typedef itk::ImageFileWriter OutputImageWriterType; + + FilterType::Pointer mainFilter = FilterType::New(); + + anima::setMultipleImageFilterInputsFromFileName(inArg.getValue(), mainFilter); + + typedef anima::GradientFileReader < std::vector < double >, double > GFReaderType; + GFReaderType gfReader; + gfReader.SetGradientFileName(gradsArg.getValue()); + gfReader.SetBValueBaseString(bvalArg.getValue()); + gfReader.SetGradientIndependentNormalization(true); + + gfReader.Update(); + + GFReaderType::GradientVectorType directions = gfReader.GetGradients(); + + for(unsigned int i = 0;i < directions.size();++i) + mainFilter->AddGradientDirection(i,directions[i]); + + GFReaderType::BValueVectorType mb = gfReader.GetBValues(); + mainFilter->SetBValuesList(mb); + + unsigned int nbCoils = numCoilsArg.getValue(); + std::vector theoreticalSnr(nbCoils,0); + for (unsigned int i = 1;i <= nbCoils;++i) + { + double beta = sqrt(M_PI/2) * std::tgamma(2*i) / pow((double)2,(int)i-1) / std::tgamma(i); + theoreticalSnr[i-1] = beta / std::sqrt(2*i-beta*beta); + std::cout << theoreticalSnr[i-1] << " "; + } + std::cout << std::endl; + + VectorImageReaderType::Pointer dtiReader = VectorImageReaderType::New(); + dtiReader->SetFileName(inDTIArg.getValue()); + dtiReader->Update(); + + mainFilter->SetDTIImage(dtiReader->GetOutput()); + + InputImageReaderType::Pointer estB0Reader = InputImageReaderType::New(); + estB0Reader->SetFileName(inB0Arg.getValue()); + estB0Reader->Update(); + + mainFilter->SetEstimatedB0Image(estB0Reader->GetOutput()); + + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + + mainFilter->SetPValueThreshold(pvThrArg.getValue()); + mainFilter->SetNumberOfCoils(nbCoils); + mainFilter->SetTheoreticalSnr(theoreticalSnr); + mainFilter->SetQuantileInitialization(quantileInitArg.getValue()); +// mainFilter->SetMedianInitialization(!quantileInitArg.isSet()); + + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + + mainFilter->Update(); + + tmpTimer.Stop(); + + std::cout << "Estimation done in " << tmpTimer.GetTotal() << " s" << std::endl; + + std::cout << "Computed Rician variance is: " << mainFilter->GetOutputVariance() << std::endl; + + std::cout << "Writing result to : " << resArg.getValue() << std::endl; + + OutputImageWriterType::Pointer maskWriter = OutputImageWriterType::New(); + maskWriter->SetInput(mainFilter->GetOutput()); + maskWriter->SetFileName(resArg.getValue()); + maskWriter->SetUseCompression(true); + + maskWriter->Update(); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/dti_estimator/CMakeLists.txt b/Anima/diffusion/dti/dti_estimator/CMakeLists.txt similarity index 100% rename from Anima/diffusion/dti_estimator/CMakeLists.txt rename to Anima/diffusion/dti/dti_estimator/CMakeLists.txt diff --git a/Anima/diffusion/dti_estimator/animaDTIEstimationImageFilter.h b/Anima/diffusion/dti/dti_estimator/animaDTIEstimationImageFilter.h similarity index 100% rename from Anima/diffusion/dti_estimator/animaDTIEstimationImageFilter.h rename to Anima/diffusion/dti/dti_estimator/animaDTIEstimationImageFilter.h diff --git a/Anima/diffusion/dti_estimator/animaDTIEstimationImageFilter.hxx b/Anima/diffusion/dti/dti_estimator/animaDTIEstimationImageFilter.hxx similarity index 100% rename from Anima/diffusion/dti_estimator/animaDTIEstimationImageFilter.hxx rename to Anima/diffusion/dti/dti_estimator/animaDTIEstimationImageFilter.hxx diff --git a/Anima/diffusion/dti_estimator/animaDTIEstimator.cxx b/Anima/diffusion/dti/dti_estimator/animaDTIEstimator.cxx similarity index 100% rename from Anima/diffusion/dti_estimator/animaDTIEstimator.cxx rename to Anima/diffusion/dti/dti_estimator/animaDTIEstimator.cxx diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/CMakeLists.txt b/Anima/diffusion/dti/dti_non_central_chi_estimator/CMakeLists.txt new file mode 100644 index 000000000..47f2063a3 --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/CMakeLists.txt @@ -0,0 +1,40 @@ +if(BUILD_TOOLS) + +project(animaDTINonCentralChiEstimator) + +## ############################################################################# +## 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} + AnimaStatisticalTests + AnimaSpecialFunctions + ${ITKIO_LIBRARIES} + ITKOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.cxx b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.cxx new file mode 100644 index 000000000..0bc448373 --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.cxx @@ -0,0 +1,88 @@ +#include "animaDTINonCentralChiCostFunction.h" +#include + +namespace anima +{ + DTINonCentralChiCostFunction::DTINonCentralChiCostFunction() + { + m_RawSignal.clear(); + m_NumberOfParameters = 7; + m_NumberOfCoils = 1; + + m_UseB0Value = false; + m_B0Value = 1; + } + + DTINonCentralChiCostFunction::MeasureType DTINonCentralChiCostFunction::GetValue(const ParametersType & parameters) const + { + unsigned int nbRawSignals = m_RawSignal.size(); + double costValue = - log(m_Sigma); + costValue *= nbRawSignals; + + for (unsigned int i = 0;i < nbRawSignals;++i) + { + double tmpVal = 0; + + double y = m_RawSignal[i]; + tmpVal += m_NumberOfCoils * log(y); + + double modelValue = 0; + for (unsigned int j = 0;j < m_NumberOfParameters;++j) + modelValue += m_DesignMatrix(i,j) * parameters[j]; + + modelValue = exp(modelValue); + if (m_UseB0Value) + modelValue *= m_B0Value; + + tmpVal -= (m_NumberOfCoils - 1) * log(modelValue); + tmpVal -= (y * y + modelValue * modelValue) / (2.0 * m_Sigma); + + y *= modelValue / m_Sigma; + tmpVal += anima::log_bessel_i(m_NumberOfCoils - 1,y); + + costValue += tmpVal; + } + + return costValue; + } + + void DTINonCentralChiCostFunction::GetDerivative(const ParametersType & parameters, DerivativeType & derivative) const + { + derivative = DerivativeType( m_NumberOfParameters ); + derivative.Fill(0); + + std::vector nuVector(m_RawSignal.size()); + vnl_matrix nuDerivativeMatrix(m_RawSignal.size(),m_NumberOfParameters); + + for (unsigned int i = 0;i < m_RawSignal.size();++i) + { + double modelValue = 0; + for (unsigned int j = 0;j < m_NumberOfParameters;++j) + modelValue += m_DesignMatrix(i,j) * parameters[j]; + + modelValue = exp(modelValue); + if (m_UseB0Value) + modelValue *= m_B0Value; + + nuVector[i] = modelValue; + + for (unsigned int j = 0;j < m_NumberOfParameters;++j) + nuDerivativeMatrix(i,j) = m_DesignMatrix(i,j) * modelValue; + } + + for (unsigned int i = 0;i < m_RawSignal.size();++i) + { + double dnuValue = - nuVector[i]; + double y = m_RawSignal[i]; + + double insideValue = y * nuVector[i] / m_Sigma; + double besselRatio = anima::bessel_ratio_i(insideValue,m_NumberOfCoils); + + dnuValue += besselRatio * y; + + for (unsigned int j = 0;j < m_NumberOfParameters;++j) + derivative[j] += nuDerivativeMatrix(i,j) * dnuValue; + } + } + +} diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.h b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.h new file mode 100644 index 000000000..57c47b299 --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiCostFunction.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace anima +{ + +class DTINonCentralChiCostFunction : + public itk::SingleValuedCostFunction +{ +public: + /** Standard class typedefs. */ + typedef DTINonCentralChiCostFunction Self; + typedef SingleValuedCostFunction Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + itkNewMacro(Self) + + /** Run-time type information (and related methods). */ + itkTypeMacro(DTINonCentralChiCostFunction, SingleValuedCostFunction) + + typedef Superclass::MeasureType MeasureType; + typedef Superclass::DerivativeType DerivativeType; + typedef Superclass::ParametersType ParametersType; + + virtual MeasureType GetValue(const ParametersType & parameters) const ITK_OVERRIDE; + virtual void GetDerivative(const ParametersType & parameters, DerivativeType & derivative) const ITK_OVERRIDE; + + // ItkSetMacro does not handle vector in debug + //itkSetMacro(RawSignal, std::vector ); + void SetRawSignal(std::vector rawSignal ){m_RawSignal = rawSignal;} + + itkSetMacro(DesignMatrix, vnl_matrix) + itkSetMacro(Sigma, double) + itkSetMacro(NumberOfParameters, unsigned int) + itkSetMacro(NumberOfCoils, unsigned int) + + itkSetMacro(B0Value, double) + itkSetMacro(UseB0Value, bool) + + unsigned int GetNumberOfParameters() const ITK_OVERRIDE + { + return m_NumberOfParameters; + } + +protected: + DTINonCentralChiCostFunction(); + + virtual ~DTINonCentralChiCostFunction() + { + } + +private: + DTINonCentralChiCostFunction(const Self&); //purposely not implemented + void operator=(const Self&); //purposely not implemented + + std::vector m_RawSignal; + unsigned int m_NumberOfParameters; + + vnl_matrix m_DesignMatrix; + double m_Sigma; + + unsigned int m_NumberOfCoils; + + bool m_UseB0Value; + double m_B0Value; +}; + +} diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.h b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.h new file mode 100644 index 000000000..c574ab79a --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include + +namespace anima +{ + +template +class DTINonCentralChiEstimationImageFilter : + public anima::MaskedImageToImageFilter< itk::Image, itk::VectorImage > +{ +public: + /** Standard class typedefs. */ + typedef DTINonCentralChiEstimationImageFilter Self; + typedef itk::Image InputImageType; + typedef itk::VectorImage OutputImageType; + typedef anima::MaskedImageToImageFilter< InputImageType, OutputImageType > 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(DTINonCentralChiEstimationImageFilter, MaskedImageToImageFilter) + + /** Image typedef support */ + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + typedef typename Superclass::MaskImageType MaskImageType; + + itkSetMacro(EulerMascheroniConstant, double) + + void SetBValuesList(std::vector bValuesList ) {m_BValuesList = bValuesList;} + + itkSetMacro(MaximumNumberOfBFGSIterations, unsigned int) + itkSetMacro(MaximumNumberOfIterations, unsigned int) + itkSetMacro(NumberOfCoils, unsigned int) + + itkSetMacro(StopThreshold, double) + itkSetMacro(BFGSStopThreshold, double) + itkSetMacro(B0Threshold, double) + + itkSetMacro(PValueThreshold, double) + itkSetMacro(B0Index, unsigned int) + + itkSetMacro(InitialDTIImage, OutputImagePointer) + itkSetMacro(InitialEstimatedB0Image, InputImagePointer) + + itkSetMacro(OptimizeB0Value, bool) + itkSetMacro(RemoveDegeneratedTensors, bool) + + itkGetMacro(EffectiveCoilsImage, InputImageType *) + itkGetMacro(LocalVarianceImage, InputImageType *) + + void AddGradientDirection(unsigned int i, std::vector &grad); + +protected: + DTINonCentralChiEstimationImageFilter() + : Superclass() + { + m_BValuesList.clear(); + m_GradientDirections.clear(); + + m_EulerMascheroniConstant = 0.577; + m_B0Threshold = 0; + m_GlobalSigma = 0; + m_BFGSStopThreshold = 10; + m_StopThreshold = 1.0e-2; + m_MaximumNumberOfBFGSIterations = 100; + m_MaximumNumberOfIterations = 100; + m_AverageBValue = 1000; + m_NumberOfCoils = 1; + + m_OptimizeB0Value = false; + + m_InitialDTIImage = NULL; + m_InitialEstimatedB0Image = NULL; + + m_EffectiveCoilsImage = NULL; + m_LocalVarianceImage = NULL; + } + + virtual ~DTINonCentralChiEstimationImageFilter() {} + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + void InitializeFromData(bool keepMask); + double ComputeLocalSigma(unsigned int nbCoils, double oldLocalSigma, double b0Value, std::vector &dtiValue, std::vector &dwi); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(DTINonCentralChiEstimationImageFilter); + + double m_EulerMascheroniConstant; + + std::vector m_BValuesList; + double m_AverageBValue; + std::vector< std::vector > m_GradientDirections; + + OutputImagePointer m_InitialDTIImage; + InputImagePointer m_InitialEstimatedB0Image; + + static const unsigned int m_NumberOfComponents = 6; + + //! Controls the number of coils, setting it to 1 goes back to Rician noise assumption + unsigned int m_NumberOfCoils; + + unsigned int m_MaximumNumberOfBFGSIterations, m_MaximumNumberOfIterations; + double m_BFGSStopThreshold; + double m_StopThreshold; + + vnl_matrix m_DesignMatrix; + double m_GlobalSigma; + bool m_OptimizeB0Value; + + // Global variance estimation parameters + double m_PValueThreshold; + double m_B0Threshold; + unsigned int m_B0Index; + + bool m_RemoveDegeneratedTensors; + + InputImagePointer m_EffectiveCoilsImage; + InputImagePointer m_LocalVarianceImage; +}; + +} // end namespace anima + +#include "animaDTINonCentralChiEstimationImageFilter.hxx" diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.hxx b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.hxx new file mode 100644 index 000000000..057bd26d2 --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimationImageFilter.hxx @@ -0,0 +1,522 @@ +#pragma once +#include "animaDTINonCentralChiEstimationImageFilter.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace anima +{ + +template +void +DTINonCentralChiEstimationImageFilter +::AddGradientDirection(unsigned int i, std::vector &grad) +{ + if (i == m_GradientDirections.size()) + m_GradientDirections.push_back(grad); + else if (i > m_GradientDirections.size()) + std::cerr << "Trying to add a direction not contiguous... Add directions contiguously (0,1,2,3,...)..." << std::endl; + else + m_GradientDirections[i] = grad; +} + +template +void +DTINonCentralChiEstimationImageFilter +::GenerateOutputInformation() +{ + // Override the method in itkImageSource, so we can set the vector length of + // the output itk::VectorImage + + this->Superclass::GenerateOutputInformation(); + + OutputImageType *output = this->GetOutput(); + output->SetVectorLength(m_NumberOfComponents); +} + +template +void +DTINonCentralChiEstimationImageFilter +::BeforeThreadedGenerateData() +{ + Superclass::BeforeThreadedGenerateData(); + + if (m_BValuesList.size() != this->GetNumberOfIndexedInputs()) + { + std::string error("There should be the same number of input images and input b-values... "); + error += m_BValuesList.size(); + error += " "; + error += this->GetNumberOfIndexedInputs(); + throw itk::ExceptionObject(__FILE__, __LINE__,error,ITK_LOCATION); + } + + // Initialize global sigma and mask from data if needed + this->InitializeFromData(!this->GetComputationMask()); + + // Create image of number of effective coils + m_EffectiveCoilsImage = InputImageType::New(); + m_EffectiveCoilsImage->Initialize(); + m_EffectiveCoilsImage->SetRegions(this->GetInput()->GetLargestPossibleRegion()); + m_EffectiveCoilsImage->SetOrigin(this->GetInput()->GetOrigin()); + m_EffectiveCoilsImage->SetDirection(this->GetInput()->GetDirection()); + m_EffectiveCoilsImage->SetSpacing(this->GetInput()->GetSpacing()); + m_EffectiveCoilsImage->Allocate(); + + m_EffectiveCoilsImage->FillBuffer(0); + + // Create image of local variance + m_LocalVarianceImage = InputImageType::New(); + m_LocalVarianceImage->Initialize(); + m_LocalVarianceImage->SetRegions(this->GetInput()->GetLargestPossibleRegion()); + m_LocalVarianceImage->SetOrigin(this->GetInput()->GetOrigin()); + m_LocalVarianceImage->SetDirection(this->GetInput()->GetDirection()); + m_LocalVarianceImage->SetSpacing(this->GetInput()->GetSpacing()); + m_LocalVarianceImage->Allocate(); + + m_LocalVarianceImage->FillBuffer(0); + + m_AverageBValue = 0; + unsigned int numBVals = 0; + for (unsigned int i = 0;i < m_GradientDirections.size();++i) + { + if (m_BValuesList[i] != 0) + { + ++numBVals; + m_AverageBValue += m_BValuesList[i]; + } + } + + m_AverageBValue /= numBVals; + for (unsigned int i = 0;i < m_GradientDirections.size();++i) + m_BValuesList[i] /= m_AverageBValue; + + // Design matrix computation + unsigned int numParameters = m_NumberOfComponents; + unsigned int numInputs = m_GradientDirections.size(); + unsigned int startPosition = 0; + if (m_OptimizeB0Value) + ++numParameters; + else + { + startPosition++; + --numInputs; + } + + m_DesignMatrix.set_size(numInputs,numParameters); + + for (unsigned int i = 0;i < numInputs;++i) + { + unsigned int pos = 0; + if (m_OptimizeB0Value) + { + m_DesignMatrix(i,pos) = 1; + ++pos; + } + + for (unsigned int j = 0;j < 3;++j) + for (unsigned int k = 0;k <= j;++k) + { + if (j != k) + m_DesignMatrix(i,pos) = - 2 * m_BValuesList[i+startPosition] * m_GradientDirections[i+startPosition][j] * m_GradientDirections[i+startPosition][k]; + else + m_DesignMatrix(i,pos) = - m_BValuesList[i+startPosition] * m_GradientDirections[i+startPosition][j] * m_GradientDirections[i+startPosition][j]; + + ++pos; + } + } +} + +template +void +DTINonCentralChiEstimationImageFilter +::InitializeFromData(bool keepMask) +{ + typedef anima::BackgroundNoiseVarianceEstimationImageFilter BackgroundVarianceFilterType; + + typename BackgroundVarianceFilterType::Pointer varianceFilter = BackgroundVarianceFilterType::New(); + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + varianceFilter->SetInput(i,this->GetInput(i)); + + varianceFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + varianceFilter->SetPValueThreshold(m_PValueThreshold); + varianceFilter->SetNumberOfCoils(1); + + varianceFilter->SetDTIImage(m_InitialDTIImage); + varianceFilter->SetEstimatedB0Image(m_InitialEstimatedB0Image); + + varianceFilter->SetBValuesList(m_BValuesList); + for (unsigned int i = 0;i < m_GradientDirections.size();++i) + varianceFilter->AddGradientDirection(i,m_GradientDirections[i]); + + varianceFilter->Update(); + + m_GlobalSigma = varianceFilter->GetOutputVariance(); + + double oldGlobalSigma = 0; + double quantile = 0.1; + while (std::abs(m_GlobalSigma - oldGlobalSigma) > m_StopThreshold * oldGlobalSigma) + { + oldGlobalSigma = m_GlobalSigma; + quantile /= 2.0; + varianceFilter->SetQuantileInitialization(quantile); + varianceFilter->Update(); + m_GlobalSigma = varianceFilter->GetOutputVariance(); + } + + std::cout << "Estimated background noise variance: " << m_GlobalSigma << std::endl; + + if (keepMask) + { + typename MaskImageType::Pointer tmpMask = varianceFilter->GetOutput(); + itk::ImageRegionIterator maskItr (tmpMask,tmpMask->GetLargestPossibleRegion()); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + maskItr.Set(1); + else + maskItr.Set(0); + ++maskItr; + } + + this->SetComputationMask(tmpMask); + } + else + { + // Just filter the mask to remove pure noise pixels where computation is useless + typename MaskImageType::Pointer tmpMask = varianceFilter->GetOutput(); + itk::ImageRegionIterator maskItr (tmpMask,tmpMask->GetLargestPossibleRegion()); + itk::ImageRegionIterator filterMaskItr (this->GetComputationMask(),tmpMask->GetLargestPossibleRegion()); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 1) + filterMaskItr.Set(0); + + ++filterMaskItr; + ++maskItr; + } + } +} + +template +void +DTINonCentralChiEstimationImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + typedef itk::ImageRegionConstIterator ImageIteratorType; + typedef itk::ImageRegionIterator EffImageIteratorType; + + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + std::vector inIterators; + for (unsigned int i = 0;i < numInputs;++i) + inIterators.push_back(ImageIteratorType(this->GetInput(i),outputRegionForThread)); + + typedef itk::ImageRegionIteratorWithIndex OutImageIteratorType; + OutImageIteratorType outIterator(this->GetOutput(),outputRegionForThread); + + typedef itk::ImageRegionConstIterator MaskImageIteratorType; + MaskImageIteratorType maskIterator(this->GetComputationMask(),outputRegionForThread); + + typedef itk::ImageRegionConstIterator DTIImageIteratorType; + DTIImageIteratorType dtiItr(m_InitialDTIImage, outputRegionForThread); + ImageIteratorType estB0Itr(m_InitialEstimatedB0Image, outputRegionForThread); + + EffImageIteratorType effImageItr(m_EffectiveCoilsImage,outputRegionForThread); + EffImageIteratorType varianceImageItr(m_LocalVarianceImage,outputRegionForThread); + + typedef typename OutputImageType::PixelType OutputPixelType; + OutputPixelType resVec(m_NumberOfComponents); + + vnl_matrix tmpTensor(3,3); + + unsigned int numParameters = m_NumberOfComponents; + if (m_OptimizeB0Value) + numParameters++; + + std::vector dtiValue(numParameters,0); + + anima::DTINonCentralChiCostFunction::Pointer cost = anima::DTINonCentralChiCostFunction::New(); + cost->SetNumberOfParameters(numParameters); + cost->SetNumberOfCoils(m_NumberOfCoils); + cost->SetDesignMatrix(m_DesignMatrix); + + cost->SetUseB0Value(!m_OptimizeB0Value); + + const double epsilon = 1.0e-16; + typedef itk::LBFGSBOptimizer OptimizerType; + + OptimizerType::Pointer opt = OptimizerType::New(); + OptimizerType::ParametersType optParameters(numParameters); + + OptimizerType::BoundValueType lowerBounds(numParameters); + OptimizerType::BoundValueType upperBounds(numParameters); + OptimizerType::BoundSelectionType boundsType(numParameters); + + // S0 bounds + unsigned int pos = 0; + if (m_OptimizeB0Value) + { + lowerBounds[pos] = 0; + upperBounds[pos] = std::log(5000.0); + boundsType[pos] = 2; + ++pos; + } + + double boundValue = 20.0; + + for (unsigned int i = 0;i < 3;++i) + for (unsigned int j = 0;j <= i;++j) + { + if (i != j) + { + lowerBounds[pos] = - boundValue; + upperBounds[pos] = boundValue; + boundsType[pos] = 2; + } + else + { + // Diagonal terms bounds + lowerBounds[pos] = 0; + upperBounds[pos] = boundValue; + boundsType[pos] = 2; + } + + ++pos; + } + + opt->SetBoundSelection(boundsType); + opt->SetLowerBound(lowerBounds); + opt->SetUpperBound(upperBounds); + + opt->SetMaximumNumberOfIterations(m_MaximumNumberOfBFGSIterations); + opt->SetMaximumNumberOfCorrections(m_MaximumNumberOfBFGSIterations); + opt->SetCostFunctionConvergenceFactor(m_BFGSStopThreshold); + + opt->SetMaximize(true); + opt->SetCostFunction(cost); + + unsigned int startPosition = 0; + if (!m_OptimizeB0Value) + startPosition++; + + unsigned int sizeDwi = numInputs - startPosition; + std::vector dwi(sizeDwi,0); + std::vector logLStensor(m_NumberOfComponents, 0); + + while (!outIterator.IsAtEnd()) + { + for (unsigned int i = 0;i < m_NumberOfComponents;++i) + resVec[i] = 0; + + if (maskIterator.Get() == 0) + { + ++maskIterator; + outIterator.Set(resVec); + ++outIterator; + + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++dtiItr; + ++estB0Itr; + ++effImageItr; + ++varianceImageItr; + + continue; + } + + // Compute the determinant of the log-LS estimated tensor + for (unsigned int i = 0;i < m_NumberOfComponents;++i) + logLStensor[i] = dtiItr.Get()[i]; + + double tensorDet = logLStensor[0] * logLStensor[2] * logLStensor[5]; + tensorDet += 2.0 * logLStensor[1] * logLStensor[3] * logLStensor[4]; + tensorDet -= logLStensor[0] * logLStensor[4] * logLStensor[4]; + tensorDet -= logLStensor[2] * logLStensor[3] * logLStensor[3]; + tensorDet -= logLStensor[5] * logLStensor[1] * logLStensor[1]; + + for (unsigned int i = startPosition;i < numInputs;++i) + dwi[i-startPosition] = std::max(epsilon,(double)inIterators[i].Get()); + + cost->SetRawSignal(dwi); + double b0Value = std::max(epsilon,(double)estB0Itr.Get()); + + if (m_OptimizeB0Value) + dtiValue[0] = log(b0Value); + else + cost->SetB0Value(b0Value); + + // Initialize with log-LS estimated tensors (or isotropic tensors if previous negative) + if (tensorDet > 0) + for (unsigned int i = 0;i < m_NumberOfComponents;++i) + dtiValue[i+m_OptimizeB0Value] = logLStensor[i] * m_AverageBValue; + else + { + dtiValue[m_OptimizeB0Value] = 0.5; + dtiValue[1+m_OptimizeB0Value] = 0; + dtiValue[2+m_OptimizeB0Value] = 0.5; + dtiValue[3+m_OptimizeB0Value] = 0; + dtiValue[4+m_OptimizeB0Value] = 0; + dtiValue[5+m_OptimizeB0Value] = 0.5; + } + + cost->SetNumberOfCoils(m_NumberOfCoils); + + unsigned int numIter = 0; + double oldLocalSigma = 0; + double localSigma; + + bool stopLoop = false; + while (!stopLoop) + { + ++numIter; + + // Compute local variance + localSigma = this->ComputeLocalSigma(m_NumberOfCoils, oldLocalSigma, b0Value, dtiValue, dwi); + + // Compute tensor + cost->SetSigma(localSigma); + + for (unsigned int i = 0;i < numParameters;++i) + optParameters[i] = dtiValue[i]; + + opt->SetInitialPosition(optParameters); + opt->StartOptimization(); + + for (unsigned int i = 0;i < numParameters;++i) + dtiValue[i] = opt->GetCurrentPosition()[i]; + + // Check stopping criteria + if (std::abs(localSigma - oldLocalSigma) < m_StopThreshold * oldLocalSigma) + stopLoop = true; + else + oldLocalSigma = localSigma; + + if (numIter > m_MaximumNumberOfIterations) + stopLoop = true; + } + + bool isTensorOk = true; + + if (m_RemoveDegeneratedTensors) + { + unsigned int pos = startPosition; + for (unsigned int i = 0;i < 3;++i) + for (unsigned int j = 0;j <= i;++j) + { + tmpTensor(i,j) = dtiValue[pos]; + if (i != j) + tmpTensor(j,i) = tmpTensor(i,j); + ++pos; + } + + vnl_symmetric_eigensystem tmpEigs(tmpTensor); + + for (unsigned int i = 0;i < 3;++i) + { + if (tmpEigs.D[i] <= 0) + { + isTensorOk = false; + break; + } + } + } + + if (isTensorOk) + { + for (unsigned int i = 0;i < m_NumberOfComponents;++i) + resVec[i] = dtiValue[i + m_OptimizeB0Value] / m_AverageBValue; + + outIterator.Set(resVec); + effImageItr.Set(m_NumberOfCoils); + varianceImageItr.Set(localSigma); + } + + for (unsigned int i = 0;i < numInputs;++i) + ++inIterators[i]; + + ++maskIterator; + ++outIterator; + ++dtiItr; + ++estB0Itr; + ++effImageItr; + ++varianceImageItr; + this->IncrementNumberOfProcessedPoints(); + } +} + +template +double +DTINonCentralChiEstimationImageFilter +::ComputeLocalSigma(unsigned int nbCoils, double oldLocalSigma, double b0Value, std::vector &dtiValue, std::vector &dwi) +{ + unsigned int numIter = 0; + unsigned int numInputs = dwi.size(); + unsigned int numParameters = dtiValue.size(); + + double resVal; + + bool stopLoop = false; + while (!stopLoop) + { + ++numIter; + + resVal = 0; + for (unsigned int i = 0;i < numInputs;++i) + { + double tmpVal = 0; + + double signalValue = 0; + for (unsigned int j = 0;j < numParameters;++j) + signalValue += m_DesignMatrix(i,j) * dtiValue[j]; + + signalValue = exp(signalValue); + if (!m_OptimizeB0Value) + signalValue *= b0Value; + + tmpVal += dwi[i] * dwi[i]; + tmpVal += signalValue * signalValue; + tmpVal /= 2.0; + + double besselRatio = 1.0; + if (oldLocalSigma > 0) + { + double insideValue = dwi[i] * signalValue / oldLocalSigma; + besselRatio = anima::bessel_ratio_i(insideValue, nbCoils); + } + + tmpVal -= besselRatio * dwi[i] * signalValue; + + resVal += tmpVal; + } + + resVal /= (numInputs * nbCoils); + + if (numIter == m_MaximumNumberOfIterations) + std::cout << "CLS" << std::endl; + + if (std::abs(resVal - oldLocalSigma) < m_StopThreshold * oldLocalSigma) + stopLoop = true; + else + oldLocalSigma = resVal; + + if (numIter > m_MaximumNumberOfIterations) + stopLoop = true; + } + + return resVal; +} + +} // end of namespace anima diff --git a/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimator.cxx b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimator.cxx new file mode 100644 index 000000000..884ec54fe --- /dev/null +++ b/Anima/diffusion/dti/dti_non_central_chi_estimator/animaDTINonCentralChiEstimator.cxx @@ -0,0 +1,182 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +double ComputeEulerMascheroniConstant(unsigned int m) +{ + double resVal = -log((double)m); + for (unsigned int i = 0;i < m;++i) + resVal += 1.0 / ((double)i+1); + + return resVal; +} + +//Update progression of the process +void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) +{ + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< inArg("i","inputdwi","DWI volume",true,"","DWI volume",cmd); + TCLAP::ValueArg resArg("o","output","Result DTI image",true,"","result DTI image",cmd); + TCLAP::ValueArg resCoilsNumArg("O","output-numcoils","Resulting image of effective number of coils per pixel",false,"","result effective coil numbers image",cmd); + TCLAP::ValueArg resLocalVarianceArg("V","output-variance","Resulting image of local variance per pixel",false,"","result local variance image",cmd); + + TCLAP::ValueArg inB0Arg("","b0","Initial estimation of B0 volume",true,"","Initial estimation of B0 volume",cmd); + TCLAP::ValueArg inDTIArg("d","dti-input","Input DTI image",true,"","input DTI image",cmd); + + TCLAP::ValueArg gradsArg("g","grad","Input gradients",true,"","Input gradients",cmd); + TCLAP::ValueArg bvalArg("b","bval","Input b-values",true,"","Input b-values",cmd); + TCLAP::ValueArg maskArg("m","mask","Computation mask",false,"","Computation mask",cmd); + + TCLAP::SwitchArg keepDegArg("K","keep-degenerated","Keep degenerated values",cmd,false); + TCLAP::SwitchArg optimizeB0Arg("B","optimize-b0","Optimize B0 value (default: no)",cmd,false); + + TCLAP::ValueArg pvThrArg("","pv","P-value threshold for mask update (default: 0.05)",false,0.05,"P-value threshold",cmd); + + TCLAP::ValueArg stopThrArg("s","stopthr","Optimization stop threshold, relative to previous value (default: 1.0e-2)",false,1.0e-2,"Optimization stop threshold",cmd); + TCLAP::ValueArg stopBFGSThrArg("","bs","BFGS Optimization stop threshold (default: 1.0e+2)",false,10,"BFGS Optimization stop threshold",cmd); + TCLAP::ValueArg maxIterBfgsArg("","ibfgs","Maximum number of iterations for BFGS (default: 100)",false,100,"Maximum number of iterations for BFGS",cmd); + TCLAP::ValueArg maxIterArg("","itmax","Maximum number of iterations (default: 100)",false,100,"Maximum number of iterations",cmd); + + TCLAP::ValueArg numCoilsArg("c","ncoils","Number of coils (default : 1 = Rician noise)",false,1,"Number of coils",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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef anima::DTINonCentralChiEstimationImageFilter FilterType; + typedef FilterType::InputImageType InputImageType; + typedef FilterType::OutputImageType VectorImageType; + + typedef itk::ImageFileReader InputImageReaderType; + typedef itk::ImageFileWriter InputImageWriterType; + typedef itk::ImageFileReader VectorImageReaderType; + typedef itk::ImageFileWriter VectorImageWriterType; + + // Handle progress + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + + FilterType::Pointer mainFilter = FilterType::New(); + + double gamma = ComputeEulerMascheroniConstant(1000000); + mainFilter->SetEulerMascheroniConstant(gamma); + + typedef anima::GradientFileReader < std::vector < double >, double > GFReaderType; + GFReaderType gfReader; + gfReader.SetGradientFileName(gradsArg.getValue()); + gfReader.SetBValueBaseString(bvalArg.getValue()); + gfReader.SetGradientIndependentNormalization(true); + + gfReader.Update(); + + GFReaderType::GradientVectorType directions = gfReader.GetGradients(); + + for(unsigned int i = 0;i < directions.size();++i) + mainFilter->AddGradientDirection(i,directions[i]); + + GFReaderType::BValueVectorType mb = gfReader.GetBValues(); + mainFilter->SetBValuesList(mb); + + anima::setMultipleImageFilterInputsFromFileName(inArg.getValue(), mainFilter); + + VectorImageReaderType::Pointer dtiReader = VectorImageReaderType::New(); + dtiReader->SetFileName(inDTIArg.getValue()); + dtiReader->Update(); + + mainFilter->SetInitialDTIImage(dtiReader->GetOutput()); + + InputImageReaderType::Pointer estB0Reader = InputImageReaderType::New(); + estB0Reader->SetFileName(inB0Arg.getValue()); + estB0Reader->Update(); + + mainFilter->SetInitialEstimatedB0Image(estB0Reader->GetOutput()); + + mainFilter->SetPValueThreshold(pvThrArg.getValue()); + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + mainFilter->SetRemoveDegeneratedTensors(!keepDegArg.isSet()); + + if (maskArg.getValue() != "") + { + typedef FilterType::MaskImageType MaskImageType; + typedef itk::ImageFileReader MaskImageReaderType; + + MaskImageReaderType::Pointer maskReader = MaskImageReaderType::New(); + maskReader->SetFileName(maskArg.getValue()); + maskReader->Update(); + mainFilter->SetComputationMask(maskReader->GetOutput()); + } + + mainFilter->SetNumberOfCoils(numCoilsArg.getValue()); + mainFilter->SetStopThreshold(stopThrArg.getValue()); + mainFilter->SetBFGSStopThreshold(stopBFGSThrArg.getValue()); + mainFilter->SetMaximumNumberOfBFGSIterations(maxIterBfgsArg.getValue()); + mainFilter->SetMaximumNumberOfIterations(maxIterArg.getValue()); + + mainFilter->SetOptimizeB0Value(optimizeB0Arg.isSet()); + + mainFilter->AddObserver(itk::ProgressEvent(), callback ); + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + + mainFilter->Update(); + + tmpTimer.Stop(); + + std::cout << "\nEstimation done in " << tmpTimer.GetTotal() << " s" << std::endl; + + std::cout << "Writing result to : " << resArg.getValue() << std::endl; + + VectorImageType::Pointer tmpOut = mainFilter->GetOutput(); + tmpOut->DisconnectPipeline(); + + VectorImageWriterType::Pointer vectorWriter = VectorImageWriterType::New(); + vectorWriter->SetInput(tmpOut); + vectorWriter->SetFileName(resArg.getValue()); + vectorWriter->SetUseCompression(true); + + vectorWriter->Update(); + + if (resCoilsNumArg.getValue() != "") + { + InputImageWriterType::Pointer numCoilsWriter = InputImageWriterType::New(); + numCoilsWriter->SetFileName(resCoilsNumArg.getValue()); + numCoilsWriter->SetInput(mainFilter->GetEffectiveCoilsImage()); + numCoilsWriter->SetUseCompression(true); + + numCoilsWriter->Update(); + } + + if (resLocalVarianceArg.getValue() != "") + { + InputImageWriterType::Pointer localVarianceWriter = InputImageWriterType::New(); + localVarianceWriter->SetFileName(resLocalVarianceArg.getValue()); + localVarianceWriter->SetInput(mainFilter->GetLocalVarianceImage()); + localVarianceWriter->SetUseCompression(true); + + localVarianceWriter->Update(); + } + + return 0; +} diff --git a/Anima/diffusion/dti_tools/dti_scalar_maps/CMakeLists.txt b/Anima/diffusion/dti/dti_scalar_maps/CMakeLists.txt similarity index 100% rename from Anima/diffusion/dti_tools/dti_scalar_maps/CMakeLists.txt rename to Anima/diffusion/dti/dti_scalar_maps/CMakeLists.txt diff --git a/Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMaps.cxx b/Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMaps.cxx similarity index 100% rename from Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMaps.cxx rename to Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMaps.cxx diff --git a/Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMapsImageFilter.h b/Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMapsImageFilter.h similarity index 100% rename from Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMapsImageFilter.h rename to Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMapsImageFilter.h diff --git a/Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMapsImageFilter.hxx b/Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMapsImageFilter.hxx similarity index 100% rename from Anima/diffusion/dti_tools/dti_scalar_maps/animaDTIScalarMapsImageFilter.hxx rename to Anima/diffusion/dti/dti_scalar_maps/animaDTIScalarMapsImageFilter.hxx diff --git a/Anima/diffusion/dti_tools/dwi_simulator_from_dti/CMakeLists.txt b/Anima/diffusion/dti/dwi_simulator_from_dti/CMakeLists.txt similarity index 100% rename from Anima/diffusion/dti_tools/dwi_simulator_from_dti/CMakeLists.txt rename to Anima/diffusion/dti/dwi_simulator_from_dti/CMakeLists.txt diff --git a/Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTI.cxx b/Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTI.cxx similarity index 100% rename from Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTI.cxx rename to Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTI.cxx diff --git a/Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.h b/Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.h similarity index 100% rename from Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.h rename to Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.h diff --git a/Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.hxx b/Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.hxx similarity index 100% rename from Anima/diffusion/dti_tools/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.hxx rename to Anima/diffusion/dti/dwi_simulator_from_dti/animaDWISimulatorFromDTIImageFilter.hxx diff --git a/Anima/diffusion/dti_tools/flip_tensors/CMakeLists.txt b/Anima/diffusion/dti/flip_tensors/CMakeLists.txt similarity index 100% rename from Anima/diffusion/dti_tools/flip_tensors/CMakeLists.txt rename to Anima/diffusion/dti/flip_tensors/CMakeLists.txt diff --git a/Anima/diffusion/dti_tools/flip_tensors/animaFlipTensorImageFilter.h b/Anima/diffusion/dti/flip_tensors/animaFlipTensorImageFilter.h similarity index 100% rename from Anima/diffusion/dti_tools/flip_tensors/animaFlipTensorImageFilter.h rename to Anima/diffusion/dti/flip_tensors/animaFlipTensorImageFilter.h diff --git a/Anima/diffusion/dti_tools/flip_tensors/animaFlipTensorImageFilter.hxx b/Anima/diffusion/dti/flip_tensors/animaFlipTensorImageFilter.hxx similarity index 100% rename from Anima/diffusion/dti_tools/flip_tensors/animaFlipTensorImageFilter.hxx rename to Anima/diffusion/dti/flip_tensors/animaFlipTensorImageFilter.hxx diff --git a/Anima/diffusion/dti_tools/flip_tensors/animaFlipTensors.cxx b/Anima/diffusion/dti/flip_tensors/animaFlipTensors.cxx similarity index 100% rename from Anima/diffusion/dti_tools/flip_tensors/animaFlipTensors.cxx rename to Anima/diffusion/dti/flip_tensors/animaFlipTensors.cxx diff --git a/Anima/diffusion/dti_tools/CMakeLists.txt b/Anima/diffusion/dti_tools/CMakeLists.txt deleted file mode 100644 index eb4d5098e..000000000 --- a/Anima/diffusion/dti_tools/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_subdirectory(dti_scalar_maps) -add_subdirectory(dwi_simulator_from_dti) -add_subdirectory(flip_tensors) diff --git a/Anima/diffusion/fibers_analysis/CMakeLists.txt b/Anima/diffusion/fibers_analysis/CMakeLists.txt index 92dc48020..fdf16d226 100644 --- a/Anima/diffusion/fibers_analysis/CMakeLists.txt +++ b/Anima/diffusion/fibers_analysis/CMakeLists.txt @@ -5,3 +5,5 @@ add_subdirectory(fibers_disease_scores) add_subdirectory(patient_to_group_comparison_on_tracks) add_subdirectory(tracks_mcm_properties_extraction) +add_subdirectory(fibers_a_contrario) +add_subdirectory(fiber_property_extract_rescale) \ No newline at end of file diff --git a/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/CMakeLists.txt b/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/CMakeLists.txt new file mode 100644 index 000000000..c21ca6e34 --- /dev/null +++ b/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/CMakeLists.txt @@ -0,0 +1,45 @@ +if(BUILD_TOOLS) + +project(animaFiberPropertyExtractAndRescale) + + +## ############################################################################# +## 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} + AnimaDataIO + AnimaTractography + ITKCommon + ${TinyXML2_LIBRARY} + ${VTK_PREFIX}CommonCore + ${VTK_PREFIX}IOXML + ${VTK_PREFIX}IOLegacy + ) + + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/animaFiberPropertyExtractAndRescale.cxx b/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/animaFiberPropertyExtractAndRescale.cxx new file mode 100644 index 000000000..037036ac0 --- /dev/null +++ b/Anima/diffusion/fibers_analysis/fiber_property_extract_rescale/animaFiberPropertyExtractAndRescale.cxx @@ -0,0 +1,118 @@ +#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 inTrackArg("i","in-tracks","input tracks name (.vtp,.vtk,.fds)",true,"","input tracks",cmd); + TCLAP::SwitchArg displayTracksDataArg("I","disp-data","display available tracks fields",cmd); + TCLAP::ValueArg numArrayArg("n","array-num","Array index to extract",false,0,"array index",cmd); + TCLAP::ValueArg minValArg("m","min-val","Minimal value",false,0.0,"minimal value",cmd); + TCLAP::ValueArg maxValArg("M","max-val","Maximal value",false,0.0,"maximal value",cmd); + TCLAP::ValueArg outTrackArg("o","out-tracks","out tracks name (.vtp,.vtk,.fds)",false,"","output tracks",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + anima::ShapesReader trackReader; + trackReader.SetFileName(inTrackArg.getValue()); + trackReader.Update(); + + vtkSmartPointer tracks = trackReader.GetOutput(); + + unsigned int numArrays = tracks->GetPointData()->GetNumberOfArrays(); + if (displayTracksDataArg.isSet()) + { + std::cout << "Tracks data file: " << inTrackArg.getValue() << std::endl; + std::cout << "Available fields and IDs: " << std::endl; + + for (unsigned int i = 0;i < numArrays;++i) + { + vtkSmartPointer workArray = vtkDoubleArray::SafeDownCast(tracks->GetPointData()->GetArray(numArrayArg.getValue())); + + if (workArray != nullptr) + std::cout << "ID: " << i << ", data field name: " << tracks->GetPointData()->GetArray(i)->GetName() << std::endl; + } + + return EXIT_SUCCESS; + } + + if (outTrackArg.getValue() == "") + { + std::cerr << "No output specified" << std::endl; + return EXIT_FAILURE; + } + + if (numArrayArg.getValue() >= numArrays) + { + std::cerr << "Array index out of bounds " << numArrays << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Extracting array " << tracks->GetPointData()->GetArray(numArrayArg.getValue())->GetName() << std::endl; + vtkSmartPointer workArray = vtkDoubleArray::SafeDownCast(tracks->GetPointData()->GetArray(numArrayArg.getValue())); + if (workArray == nullptr) + { + std::cerr << "Incompatible array for double conversion" << std::endl; + return EXIT_FAILURE; + } + + double minValue = minValArg.getValue(); + double maxValue = maxValArg.getValue(); + + if (minValue == maxValue) + { + // Compute min and max + minValue = workArray->GetValue(0); + maxValue = minValue; + for (unsigned int i = 1;i < workArray->GetNumberOfValues();++i) + { + double val = workArray->GetValue(i); + if (minValue > val) + minValue = val; + + if (maxValue < val) + maxValue = val; + } + } + + std::cout << "Minimal value: " << minValue << ", maximal value: " << maxValue << std::endl; + + for (unsigned int i = 0;i < workArray->GetNumberOfValues();++i) + { + double val = workArray->GetValue(i); + if (val <= minValue) + workArray->SetValue(i,0.0); + else if (val >= maxValue) + workArray->SetValue(i,1.0); + else + workArray->SetValue(i,(val - minValue) / (maxValue - minValue)); + } + + for (int i = numArrays;i > 0;--i) + tracks->GetPointData()->RemoveArray(i-1); + + tracks->GetPointData()->AddArray(workArray); + + anima::ShapesWriter writer; + writer.SetInputData(tracks); + writer.SetFileName(outTrackArg.getValue()); + std::cout << "Writing tracks : " << outTrackArg.getValue() << std::endl; + writer.Update(); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/fibers_analysis/fibers_a_contrario/CMakeLists.txt b/Anima/diffusion/fibers_analysis/fibers_a_contrario/CMakeLists.txt new file mode 100644 index 000000000..cbc242c14 --- /dev/null +++ b/Anima/diffusion/fibers_analysis/fibers_a_contrario/CMakeLists.txt @@ -0,0 +1,47 @@ +if(BUILD_TOOLS) + +if (USE_VTK AND VTK_FOUND) + +project(animaFibersAContrario) + +## ############################################################################# +## 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} + AnimaDataIO + AnimaStatisticalTests + ITKStatistics + ${VTK_PREFIX}CommonCore + ${VTK_PREFIX}CommonDataModel + ${VTK_PREFIX}IOXML + ${VTK_PREFIX}IOLegacy + ${TinyXML2_LIBRARY} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() + +endif() diff --git a/Anima/diffusion/fibers_analysis/fibers_a_contrario/animaFibersAContrario.cxx b/Anima/diffusion/fibers_analysis/fibers_a_contrario/animaFibersAContrario.cxx new file mode 100644 index 000000000..5a86b4728 --- /dev/null +++ b/Anima/diffusion/fibers_analysis/fibers_a_contrario/animaFibersAContrario.cxx @@ -0,0 +1,216 @@ +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void aContrario(unsigned int index, vtkPolyData *tracks, vtkPolyData *resultTracks, std::vector &usefulArrays, + itk::Statistics::KdTree < itk::Statistics::ListSample < itk::Point > > *kdTree, double searchRadius, + itk::Statistics::KdTree < itk::Statistics::ListSample < itk::Point > >::InstanceIdentifierVectorType &workData, + double rareEventThr) +{ + using PointType = itk::Point ; + PointType dataPoint; + double tmpVTKPoint[3]; + + tracks->GetPoints()->GetPoint(index, tmpVTKPoint); + for (unsigned int j = 0;j < 3;++j) + dataPoint[j] = tmpVTKPoint[j]; + + kdTree->Search(dataPoint, searchRadius, workData); + + unsigned int numSelectedData = workData.size(); + unsigned int numArrays = usefulArrays.size(); + + // Counting rare events for each array + for (unsigned int j = 0;j < numArrays;++j) + { + // Rare events count corresponds to L(r) in Maumet et al Neuroimage paper + // dataTracksCopy will contain 1 if different from H0, 0 if not + unsigned int rareEventsCount = 0; + vtkDoubleArray *currentArray = dynamic_cast (tracks->GetPointData()->GetArray(usefulArrays[j])); + vtkDoubleArray *currentCopyArray = dynamic_cast (resultTracks->GetPointData()->GetArray(usefulArrays[j])); + for (unsigned int k = 0;k < numSelectedData;++k) + { + if (currentArray->GetValue(workData[k]) <= rareEventThr) + ++rareEventsCount; + } + + double nfa = 1.0; + if (rareEventsCount > 0) + nfa = boost::math::cdf(boost::math::complement(boost::math::binomial(numSelectedData, rareEventThr), rareEventsCount - 1)); + + currentCopyArray->SetValue(index,nfa); + } +} + +struct ThreaderArguments +{ + using PointType = itk::Point ; + using ListSampleType = itk::Statistics::ListSample ; + using KdTreeType = itk::Statistics::KdTree ; + + vtkPolyData *dataTracks; + vtkPolyData *dataTracksCopy; + std::vector usefulArrays; + KdTreeType *kdTree; + double searchRadius; + double rareEventThr; +}; + +ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION ThreadAContrario(void *arg) +{ + itk::MultiThreaderBase::WorkUnitInfo *threadArgs = (itk::MultiThreaderBase::WorkUnitInfo *)arg; + unsigned int nbThread = threadArgs->WorkUnitID; + unsigned int numTotalThread = threadArgs->NumberOfWorkUnits; + + ThreaderArguments *tmpArg = static_cast (threadArgs->UserData); + unsigned int nbTotalPoints = tmpArg->dataTracks->GetNumberOfPoints(); + + unsigned int step = nbTotalPoints / numTotalThread; + unsigned int startIndex = nbThread * step; + unsigned int endIndex = (nbThread + 1) * step; + + if (nbThread == numTotalThread - 1) + endIndex = nbTotalPoints; + + using PointType = itk::Point ; + using ListSampleType = itk::Statistics::ListSample ; + using KdTreeType = itk::Statistics::KdTree ; + + KdTreeType::InstanceIdentifierVectorType workData; + for (unsigned int i = startIndex;i < endIndex;++i) + aContrario(i, tmpArg->dataTracks, tmpArg->dataTracksCopy, tmpArg->usefulArrays, tmpArg->kdTree, + tmpArg->searchRadius, workData, tmpArg->rareEventThr); + + return ITK_THREAD_RETURN_DEFAULT_VALUE; +} + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg inArg("i","input","Non corrected p-value fibers",true,"","Non corrected p-value fibers",cmd); + TCLAP::ValueArg resArg("o","output","FDR thresholded output fibers at q",true,"","FDR corrected output fibers at q",cmd); + TCLAP::ValueArg radiusArg("r","radius","Local ball radius in millimeters (default: 2.0)",false,2.0,"radius of ball around fiber point",cmd); + TCLAP::ValueArg rareEventThrArg("t","rare-thr","P-value threshold to consider rare event (default: 0.05)",false,0.05,"p-value threshold for rare event",cmd); + + TCLAP::ValueArg qArg("q","q-val","FDR q value",false,0.05,"FDR q value",cmd); + TCLAP::SwitchArg byCorrArg("Y", "by-corr", "Use BY correction (if not set, BH correction is used)", cmd, false); + + TCLAP::ValueArg nbThreadsArg("T","nb-threads","Number of threads to run on (default: all available)",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; + } + + anima::ShapesReader trackReader; + trackReader.SetFileName(inArg.getValue()); + trackReader.Update(); + + using PolyDataPointer = vtkSmartPointer ; + PolyDataPointer dataTracks = trackReader.GetOutput(); + // Data tracks copy will contain rare events count + PolyDataPointer dataTracksCopy = vtkPolyData::New(); + dataTracksCopy->DeepCopy(dataTracks); + + // Get dummy cell so that it's thread safe + vtkSmartPointer dummyCell = vtkGenericCell::New(); + dataTracks->GetCell(0,dummyCell); + dataTracksCopy->GetCell(0,dummyCell); + + vtkIdType nbTotalPts = dataTracks->GetNumberOfPoints(); + unsigned int numArrays = dataTracks->GetPointData()->GetNumberOfArrays(); + std::vector usefulArrays; + for (unsigned int i = 0;i < numArrays;++i) + { + if (dataTracks->GetPointData()->GetArray(i)->GetNumberOfComponents() == 1) + usefulArrays.push_back(i); + } + + numArrays = usefulArrays.size(); + + // Construct kd-tree of points + using PointType = itk::Point ; + using ListSampleType = itk::Statistics::ListSample ; + using KdTreeType = itk::Statistics::KdTree ; + using KdTreePointer = KdTreeType::Pointer; + using TreeGeneratorType = itk::Statistics::KdTreeGenerator ; + + ListSampleType::Pointer treeData = ListSampleType::New(); + treeData->Resize(nbTotalPts); + + PointType dataPoint; + double tmpVTKPoint[3]; + for (unsigned int i = 0;i < nbTotalPts;++i) + { + dataTracks->GetPoints()->GetPoint(i,tmpVTKPoint); + for (unsigned int j = 0;j < 3;++j) + dataPoint[j] = tmpVTKPoint[j]; + treeData->SetMeasurementVector(i,dataPoint); + } + + TreeGeneratorType::Pointer treeGenerator = TreeGeneratorType::New(); + treeGenerator->SetSample(treeData); + treeGenerator->SetBucketSize(16); + treeGenerator->Update(); + + ThreaderArguments tmpStr; + tmpStr.dataTracks = dataTracks; + tmpStr.dataTracksCopy = dataTracksCopy; + tmpStr.usefulArrays = usefulArrays; + tmpStr.kdTree = treeGenerator->GetOutput(); + tmpStr.searchRadius = radiusArg.getValue(); + tmpStr.rareEventThr = rareEventThrArg.getValue(); + + itk::PoolMultiThreader::Pointer mThreader = itk::PoolMultiThreader::New(); + mThreader->SetNumberOfWorkUnits(nbThreadsArg.getValue()); + mThreader->SetSingleMethod(ThreadAContrario, &tmpStr); + mThreader->SingleMethodExecute(); + + // Now we have the new p-values map, let's go for FDR correction + std::vector pValuesVector(nbTotalPts); + for (unsigned int j = 0;j < numArrays;++j) + { + unsigned int jIndex = usefulArrays[j]; + vtkDoubleArray *currentArray = dynamic_cast (dataTracksCopy->GetPointData()->GetArray(jIndex)); + + for (unsigned int i = 0;i < nbTotalPts;++i) + pValuesVector[i] = currentArray->GetValue(i); + + if (byCorrArg.isSet()) + anima::BYCorrection(pValuesVector, qArg.getValue()); + else + anima::BHCorrection(pValuesVector, qArg.getValue()); + + for (unsigned int j = 0;j < nbTotalPts;++j) + currentArray->SetValue(j,pValuesVector[j]); + } + + anima::ShapesWriter writer; + writer.SetInputData(dataTracksCopy); + writer.SetFileName(resArg.getValue()); + writer.Update(); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/mcm/CMakeLists.txt b/Anima/diffusion/mcm/CMakeLists.txt index 14ba1ea68..26f664bd4 100644 --- a/Anima/diffusion/mcm/CMakeLists.txt +++ b/Anima/diffusion/mcm/CMakeLists.txt @@ -1,4 +1,4 @@ -project(AnimaMCM) +project(AnimaMCMPrivate) ## ############################################################################# ## List Sources @@ -23,8 +23,9 @@ add_library(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} AnimaMCMBase + AnimaMCM AnimaSpecialFunctions - AnimaOptimizers + ITKOptimizers ITKCommon ${TinyXML2_LIBRARY} ) diff --git a/Anima/diffusion/mcm/animaDDICompartment.cxx b/Anima/diffusion/mcm/animaDDICompartment.cxx new file mode 100644 index 000000000..502a38168 --- /dev/null +++ b/Anima/diffusion/mcm/animaDDICompartment.cxx @@ -0,0 +1,325 @@ +#include +#include +#include + +namespace anima +{ + +double DDICompartment::GetFourierTransformedDiffusionProfile(double smallDelta, double bigDelta, double gradientStrength, + const Vector3DType &gradient) +{ + double bValue = anima::GetBValueFromAcquisitionParameters(smallDelta, bigDelta, gradientStrength); + + Vector3DType compartmentOrientation(0.0); + anima::TransformSphericalToCartesianCoordinates(this->GetOrientationTheta(),this->GetOrientationPhi(),1.0,compartmentOrientation); + + double resVal = anima::ComputeSymmetricCDF(compartmentOrientation,this->GetOrientationConcentration(),this->GetAxialDiffusivity(), + this->GetExtraAxonalFraction(),bValue,gradient); + return resVal; +} + +DDICompartment::ListType &DDICompartment::GetSignalAttenuationJacobian(double smallDelta, double bigDelta, double gradientStrength, + const Vector3DType &gradient) +{ + m_JacobianVector.resize(this->GetNumberOfParameters()); + std::fill(m_JacobianVector.begin(), m_JacobianVector.end(), 0.0); + + return m_JacobianVector; +} + +double DDICompartment::GetLogDiffusionProfile(const Vector3DType &sample) +{ + Vector3DType compartmentOrientation(0.0); + anima::TransformSphericalToCartesianCoordinates(this->GetOrientationTheta(),this->GetOrientationPhi(),1.0,compartmentOrientation); + + double integrationStep = 0.1; + unsigned int integrandSize = (unsigned int)(1 / integrationStep + 1); + std::vector integrand(integrandSize,0); + + double pdfValue = anima::ComputeSymmetricPDF(sample,compartmentOrientation,this->GetOrientationConcentration(),this->GetAxialDiffusivity(), + this->GetExtraAxonalFraction(),integrationStep,integrand); + + return std::log(pdfValue); +} + +void DDICompartment::SetParametersFromVector(const ListType ¶ms) +{ + if (params.size() != this->GetNumberOfParameters()) + return; + + this->SetOrientationTheta(params[0]); + this->SetOrientationPhi(params[1]); + + unsigned int currentPos = 2; + if (m_EstimateOrientationConcentration) + { + this->SetOrientationConcentration(params[currentPos]); + ++currentPos; + } + + if (m_EstimateAxialDiffusivity) + { + this->SetAxialDiffusivity(params[currentPos]); + ++currentPos; + } + + if (m_EstimateExtraAxonalFraction) + this->SetExtraAxonalFraction(params[currentPos]); + + if (!m_EstimateOrientationConcentration) + { + double defaultKappa = this->GetAxialDiffusivity() / ((this->GetRadialDiffusivity1() + this->GetRadialDiffusivity2()) / 2.0) - 1.0; + this->SetOrientationConcentration(std::max(anima::MCMEpsilon,defaultKappa)); + } + + if (!m_EstimateExtraAxonalFraction) + { + double xiVal = anima::xi(this->GetOrientationConcentration()); + double defaultKappa = this->GetAxialDiffusivity() / ((this->GetRadialDiffusivity1() + this->GetRadialDiffusivity2()) / 2.0) - 1.0; + double eaf = (this->GetOrientationConcentration() - defaultKappa) / + ((this->GetOrientationConcentration() + 1.0) * (defaultKappa + 3.0) * xiVal - defaultKappa - 1.0); + + eaf = std::min(1.0 - anima::MCMEpsilon,std::max(anima::MCMEpsilon,eaf)); + + this->SetExtraAxonalFraction(eaf); + } + + if (!m_EstimateAxialDiffusivity) + { + double xiVal = anima::xi(this->GetOrientationConcentration()); + double axialDiff = 1.71e-3 / (1.0 - 2.0 * this->GetExtraAxonalFraction() * xiVal); + + this->SetAxialDiffusivity(axialDiff); + } +} + +DDICompartment::ListType &DDICompartment::GetParametersAsVector() +{ + m_ParametersVector.resize(this->GetNumberOfParameters()); + + m_ParametersVector[0] = this->GetOrientationTheta(); + m_ParametersVector[1] = this->GetOrientationPhi(); + + unsigned int currentPos = 2; + if (m_EstimateOrientationConcentration) + { + m_ParametersVector[currentPos] = this->GetOrientationConcentration(); + ++currentPos; + } + + if (m_EstimateAxialDiffusivity) + { + m_ParametersVector[currentPos] = this->GetAxialDiffusivity(); + ++currentPos; + } + + if (m_EstimateExtraAxonalFraction) + m_ParametersVector[currentPos] = this->GetExtraAxonalFraction(); + + return m_ParametersVector; +} + +DDICompartment::ListType &DDICompartment::GetParameterLowerBounds() +{ + m_ParametersLowerBoundsVector.resize(this->GetNumberOfParameters()); + + m_ParametersLowerBoundsVector[0] = anima::MCMZeroLowerBound; + m_ParametersLowerBoundsVector[1] = anima::MCMZeroLowerBound; + + unsigned int currentPos = 2; + if (m_EstimateOrientationConcentration) + { + m_ParametersLowerBoundsVector[currentPos] = anima::MCMZeroLowerBound; + ++currentPos; + } + + if (m_EstimateAxialDiffusivity) + { + m_ParametersLowerBoundsVector[currentPos] = anima::MCMDiffusivityLowerBound; + ++currentPos; + } + + if (m_EstimateExtraAxonalFraction) + m_ParametersLowerBoundsVector[currentPos] = anima::MCMEpsilon; + + return m_ParametersLowerBoundsVector; +} + +DDICompartment::ListType &DDICompartment::GetParameterUpperBounds() +{ + m_ParametersUpperBoundsVector.resize(this->GetNumberOfParameters()); + + m_ParametersUpperBoundsVector[0] = anima::MCMPolarAngleUpperBound; + m_ParametersUpperBoundsVector[1] = anima::MCMAzimuthAngleUpperBound; + + unsigned int currentPos = 2; + if (m_EstimateOrientationConcentration) + { + m_ParametersUpperBoundsVector[currentPos] = anima::MCMConcentrationUpperBound; + ++currentPos; + } + + if (m_EstimateAxialDiffusivity) + { + m_ParametersUpperBoundsVector[currentPos] = anima::MCMDiffusivityUpperBound; + ++currentPos; + } + + if (m_EstimateExtraAxonalFraction) + m_ParametersUpperBoundsVector[currentPos] = 1.0 - anima::MCMEpsilon; + + return m_ParametersUpperBoundsVector; +} + +void DDICompartment::SetEstimateOrientationConcentration(bool arg) +{ + if (m_EstimateOrientationConcentration == arg) + return; + + m_EstimateOrientationConcentration = arg; + m_ChangedConstraints = true; +} + +void DDICompartment::SetEstimateAxialDiffusivity(bool arg) +{ + if (m_EstimateAxialDiffusivity == arg) + return; + + m_EstimateAxialDiffusivity = arg; + m_ChangedConstraints = true; +} + +void DDICompartment::SetEstimateExtraAxonalFraction(bool arg) +{ + if (m_EstimateExtraAxonalFraction == arg) + return; + + m_EstimateExtraAxonalFraction = arg; + m_ChangedConstraints = true; +} + +void DDICompartment::SetCompartmentVector(ModelOutputVectorType &compartmentVector) +{ + if (compartmentVector.GetSize() != this->GetCompartmentSize()) + itkExceptionMacro("The input vector size does not match the size of the compartment"); + + Vector3DType compartmentOrientation, sphDir; + + for (unsigned int i = 0;i < m_SpaceDimension;++i) + compartmentOrientation[i] = compartmentVector[i]; + + anima::TransformCartesianToSphericalCoordinates(compartmentOrientation,sphDir); + this->SetOrientationTheta(sphDir[0]); + this->SetOrientationPhi(sphDir[1]); + + unsigned int currentPos = 3; + this->SetOrientationConcentration(compartmentVector[currentPos]); + ++currentPos; + + this->SetAxialDiffusivity(compartmentVector[currentPos]); + ++currentPos; + + this->SetExtraAxonalFraction(compartmentVector[currentPos]); +} + +unsigned int DDICompartment::GetCompartmentSize() +{ + return 6; +} + +unsigned int DDICompartment::GetNumberOfParameters() +{ + if (!m_ChangedConstraints) + return m_NumberOfParameters; + + m_NumberOfParameters = 2; + + if (m_EstimateOrientationConcentration) + ++m_NumberOfParameters; + + if (m_EstimateAxialDiffusivity) + ++m_NumberOfParameters; + + if (m_EstimateExtraAxonalFraction) + ++m_NumberOfParameters; + + return m_NumberOfParameters; +} + +DDICompartment::ModelOutputVectorType &DDICompartment::GetCompartmentVector() +{ + if (m_CompartmentVector.GetSize() != this->GetCompartmentSize()) + m_CompartmentVector.SetSize(this->GetCompartmentSize()); + + Vector3DType compartmentOrientation(0.0); + anima::TransformSphericalToCartesianCoordinates(this->GetOrientationTheta(),this->GetOrientationPhi(),1.0,compartmentOrientation); + + for (unsigned int i = 0;i < m_SpaceDimension;++i) + m_CompartmentVector[i] = compartmentOrientation[i]; + + unsigned int currentPos = 3; + m_CompartmentVector[currentPos] = this->GetOrientationConcentration(); + ++currentPos; + + m_CompartmentVector[currentPos] = this->GetAxialDiffusivity(); + ++currentPos; + + m_CompartmentVector[currentPos] = this->GetExtraAxonalFraction(); + + return m_CompartmentVector; +} + +double DDICompartment::GetApparentFractionalAnisotropy() +{ + double kappaVal = this->GetOrientationConcentration(); + double xiVal = anima::xi(kappaVal); + + double dVal = this->GetAxialDiffusivity(); + double aVal = this->GetExtraAxonalFraction(); + + double voxelAxialDiffusivity = dVal * (1.0 - 2.0 * aVal * xiVal); + double voxelRadialDiffusivity = dVal * ((1.0 - aVal) / (kappaVal + 1.0) + aVal * xiVal); + + double diffVal = voxelAxialDiffusivity - voxelRadialDiffusivity; + double normVal = std::sqrt(voxelAxialDiffusivity * voxelAxialDiffusivity + 2.0 * voxelRadialDiffusivity * voxelRadialDiffusivity); + + return diffVal / normVal; +} + +double DDICompartment::GetApparentMeanDiffusivity() +{ + double kappaVal = this->GetOrientationConcentration(); + double xiVal = anima::xi(kappaVal); + + double dVal = this->GetAxialDiffusivity(); + double aVal = this->GetExtraAxonalFraction(); + + double voxelAxialDiffusivity = dVal * (1.0 - 2.0 * aVal * xiVal); + double voxelRadialDiffusivity = dVal * ((1.0 - aVal) / (kappaVal + 1.0) + aVal * xiVal); + + return (voxelAxialDiffusivity + 2.0 * voxelRadialDiffusivity) / 3.0; +} + +double DDICompartment::GetApparentParallelDiffusivity() +{ + double kappaVal = this->GetOrientationConcentration(); + double xiVal = anima::xi(kappaVal); + + double dVal = this->GetAxialDiffusivity(); + double aVal = this->GetExtraAxonalFraction(); + + return dVal * (1.0 - 2.0 * aVal * xiVal); +} + +double DDICompartment::GetApparentPerpendicularDiffusivity() +{ + double kappaVal = this->GetOrientationConcentration(); + double xiVal = anima::xi(kappaVal); + + double dVal = this->GetAxialDiffusivity(); + double aVal = this->GetExtraAxonalFraction(); + + return dVal * ((1.0 - aVal) / (kappaVal + 1.0) + aVal *xiVal); +} + +} // end namespace anima diff --git a/Anima/diffusion/mcm/animaDDICompartment.h b/Anima/diffusion/mcm/animaDDICompartment.h new file mode 100644 index 000000000..2b1e010bb --- /dev/null +++ b/Anima/diffusion/mcm/animaDDICompartment.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +namespace anima +{ + +class ANIMAMCM_EXPORT DDICompartment : public BaseCompartment +{ +public: + // Useful typedefs + typedef DDICompartment Self; + typedef BaseCompartment Superclass; + typedef Superclass::Pointer BasePointer; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + typedef Superclass::ModelOutputVectorType ModelOutputVectorType; + typedef Superclass::Vector3DType Vector3DType; + + // New macro + itkNewMacro(Self) + + /** Run-time type information (and related methods) */ + itkTypeMacro(DDICompartment, BaseCompartment) + + DiffusionModelCompartmentType GetCompartmentType() ITK_OVERRIDE {return DDI;} + + virtual double GetFourierTransformedDiffusionProfile(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient) ITK_OVERRIDE; + virtual ListType &GetSignalAttenuationJacobian(double smallDelta, double bigDelta, double gradientStrength, const Vector3DType &gradient) ITK_OVERRIDE; + virtual double GetLogDiffusionProfile(const Vector3DType &sample) ITK_OVERRIDE; + + virtual void SetParametersFromVector(const ListType ¶ms) ITK_OVERRIDE; + virtual ListType &GetParametersAsVector() ITK_OVERRIDE; + + virtual ListType &GetParameterLowerBounds() ITK_OVERRIDE; + virtual ListType &GetParameterUpperBounds() ITK_OVERRIDE; + + // Set constraints + void SetEstimateOrientationConcentration(bool arg); + void SetEstimateAxialDiffusivity(bool arg); + void SetEstimateExtraAxonalFraction(bool arg); + + void SetCompartmentVector(ModelOutputVectorType &compartmentVector) ITK_OVERRIDE; + + bool GetTensorCompatible() ITK_OVERRIDE {return false;} + unsigned int GetCompartmentSize() ITK_OVERRIDE; + unsigned int GetNumberOfParameters() ITK_OVERRIDE; + ModelOutputVectorType &GetCompartmentVector() ITK_OVERRIDE; + + // Specific info on compartment (might be brought back to BaseCompartment some day) + double GetApparentFractionalAnisotropy() ITK_OVERRIDE; + double GetApparentMeanDiffusivity() ITK_OVERRIDE; + double GetApparentParallelDiffusivity() ITK_OVERRIDE; + double GetApparentPerpendicularDiffusivity() ITK_OVERRIDE; + +protected: + DDICompartment() : Superclass() + { + m_EstimateOrientationConcentration = true; + m_EstimateAxialDiffusivity = true; + m_EstimateExtraAxonalFraction = true; + m_ChangedConstraints = true; + } + + virtual ~DDICompartment() {} + +private: + bool m_EstimateOrientationConcentration, m_EstimateAxialDiffusivity, m_EstimateExtraAxonalFraction; + bool m_ChangedConstraints; + + unsigned int m_NumberOfParameters; +}; + +} //end namespace anima + diff --git a/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.cxx b/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.cxx index f66342486..45bf84a1c 100644 --- a/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.cxx +++ b/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.cxx @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -223,8 +224,31 @@ void MultiCompartmentModelCreator::CreateNODDICompartment(BaseCompartmentPointer void MultiCompartmentModelCreator::CreateDDICompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints) { - std::string error("DDI model not implemented in the public version of ANIMA"); - throw itk::ExceptionObject(__FILE__, __LINE__,error,ITK_LOCATION); + typedef anima::DDICompartment DDIType; + + DDIType::Pointer ddiComp = DDIType::New(); + ddiComp->SetEstimateOrientationConcentration(!this->GetUseConstrainedOrientationConcentration()); + ddiComp->SetEstimateAxialDiffusivity(!this->GetUseConstrainedDiffusivity()); + ddiComp->SetEstimateExtraAxonalFraction(!this->GetUseConstrainedExtraAxonalFraction()); + + ddiComp->SetOrientationConcentration(this->GetOrientationConcentration()); + ddiComp->SetAxialDiffusivity(this->GetAxialDiffusivity()); + ddiComp->SetRadialDiffusivity1((this->GetRadialDiffusivity1() + this->GetRadialDiffusivity2()) / 2.0); + ddiComp->SetExtraAxonalFraction(this->GetExtraAxonalFraction()); + + if (applyConstraints) + { + if (this->GetUseCommonDiffusivities()) + ddiComp->SetEstimateAxialDiffusivity(false); + + if (this->GetUseCommonConcentrations()) + ddiComp->SetEstimateOrientationConcentration(false); + + if (this->GetUseCommonExtraAxonalFractions()) + ddiComp->SetEstimateExtraAxonalFraction(false); + } + + compartmentPointer = ddiComp; } } // end namespace anima diff --git a/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.h b/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.h index 4355f7fe1..093fde41d 100644 --- a/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.h +++ b/Anima/diffusion/mcm/animaMultiCompartmentModelCreator.h @@ -72,7 +72,7 @@ class ANIMAMCM_EXPORT MultiCompartmentModelCreator void CreateZeppelinCompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints); void CreateTensorCompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints); void CreateNODDICompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints); - virtual void CreateDDICompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints); + void CreateDDICompartment(BaseCompartmentPointer &compartmentPointer, bool applyConstraints); CompartmentType m_CompartmentType; bool m_ModelWithFreeWaterComponent, m_ModelWithStationaryWaterComponent; diff --git a/Anima/diffusion/mcm/animaNonCentralChiMCMCost.cxx b/Anima/diffusion/mcm/animaNonCentralChiMCMCost.cxx new file mode 100644 index 000000000..94570140b --- /dev/null +++ b/Anima/diffusion/mcm/animaNonCentralChiMCMCost.cxx @@ -0,0 +1,89 @@ +#include +#include +#include + +namespace anima +{ + +NonCentralChiMCMCost::MeasureType NonCentralChiMCMCost::GetValues(const ParametersType ¶meters) +{ + unsigned int numberOfParameters = parameters.GetSize(); + // Set MCM parameters + m_TestedParameters.resize(numberOfParameters); + + for (unsigned int i = 0;i < numberOfParameters;++i) + m_TestedParameters[i] = parameters[i]; + + // fake return, for now + MeasureType tmpResults; + return tmpResults; +} + +double NonCentralChiMCMCost::GetCurrentCostValue() +{ + unsigned int nbImages = m_Gradients.size(); + + m_MCMStructure->SetParametersFromVector(m_TestedParameters); + + m_PredictedSignals.resize(nbImages); + for (unsigned int i = 0;i < nbImages;++i) + m_PredictedSignals[i] = m_MCMStructure->GetPredictedSignal(m_SmallDelta,m_BigDelta, + m_GradientStrengths[i],m_Gradients[i]); + + this->ComputeSigmaSquareValue(); + + double costFunctionValue = - nbImages * std::log(m_SigmaSquare); + + for (unsigned int i = 0;i < nbImages;++i) + { + double predictedValue = m_PredictedSignals[i]; + double observedValue = std::max(1.0e-4,m_ObservedSignals[i]); + + costFunctionValue -= (observedValue * observedValue + predictedValue * predictedValue) / (2.0 * m_SigmaSquare); + costFunctionValue += (2.0 * m_NumberOfCoils - 1.0) * std::log(observedValue); + costFunctionValue += anima::log_bessel_i_lower_bound(m_NumberOfCoils - 1, predictedValue * observedValue / m_SigmaSquare) + (1.0 - m_NumberOfCoils) * std::log(predictedValue * observedValue); + } + + if (!std::isfinite(costFunctionValue)) + { + std::cout << "Log cost function: " << costFunctionValue << std::endl; + itkExceptionMacro("Non finite log cost function"); + } + + // Multiply result by -2.0 as the cost function has to be maximized, and AIC can use it directly + return - 2.0 * costFunctionValue; +} + +void NonCentralChiMCMCost::ComputeSigmaSquareValue() +{ + unsigned int nbImages = m_Gradients.size(); + + double constantPartSigmaSq = 0; + for (unsigned int i = 0;i < nbImages;++i) + constantPartSigmaSq += m_ObservedSignals[i] * m_ObservedSignals[i] + m_PredictedSignals[i] * m_PredictedSignals[i]; + + constantPartSigmaSq /= 2.0 * nbImages * m_NumberOfCoils; + + // Initializing at half the upper bound of noise value + m_SigmaSquare = constantPartSigmaSq / 2.0; + double previousSigmaSquare = m_SigmaSquare; + + double diffValue = 1.0; + while (diffValue > 1.0e-6) + { + previousSigmaSquare = m_SigmaSquare; + double nonConstantPart = 0.0; + for (unsigned int i = 0;i < nbImages;++i) + { + double logNonConstantValue = std::log(2.0) + std::log(m_ObservedSignals[i]) + std::log(m_PredictedSignals[i]); + logNonConstantValue += anima::bessel_ratio_i_lower_bound(m_NumberOfCoils, m_ObservedSignals[i] * m_PredictedSignals[i] / previousSigmaSquare); + + nonConstantPart += std::exp(logNonConstantValue); + } + + m_SigmaSquare = constantPartSigmaSq - nonConstantPart / (2.0 * nbImages * m_NumberOfCoils); + diffValue = std::abs(m_SigmaSquare - previousSigmaSquare); + } +} + +} // end namespace anima diff --git a/Anima/diffusion/mcm/animaNonCentralChiMCMCost.h b/Anima/diffusion/mcm/animaNonCentralChiMCMCost.h new file mode 100644 index 000000000..cc1a7d05d --- /dev/null +++ b/Anima/diffusion/mcm/animaNonCentralChiMCMCost.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +namespace anima +{ + +class ANIMAMCM_EXPORT NonCentralChiMCMCost : public anima::BaseMCMCost +{ +public: + /** Standard class typedefs. */ + typedef NonCentralChiMCMCost Self; + typedef anima::BaseMCMCost Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + itkNewMacro(Self) + + /** Run-time type information (and related methods). */ + itkTypeMacro(NonCentralChiMCMCost, anima::BaseMCMCost) + + void SetNumberOfCoils(unsigned int val) {m_NumberOfCoils = val;} + + //! Not really doing anything, only setting parameters for now + MeasureType GetValues(const ParametersType ¶meters) ITK_OVERRIDE; + + //! For the current set of parameters, compute the cost function value, requires GetValues to be called first + double GetCurrentCostValue() ITK_OVERRIDE; + + //! Purposedly not implemented + void GetDerivativeMatrix(const ParametersType ¶meters, DerivativeMatrixType &derivative) ITK_OVERRIDE {} + + //! Derivative, not implemented yet + void GetCurrentDerivative(DerivativeMatrixType &derivativeMatrix, DerivativeType &derivative) ITK_OVERRIDE + { + itkExceptionMacro("Derivative not implemented for non-central chi noise"); + } + +protected: + NonCentralChiMCMCost() + { + m_NumberOfCoils = 1; + } + + virtual ~NonCentralChiMCMCost() {} + void ComputeSigmaSquareValue(); + +private: + NonCentralChiMCMCost(const Self&); //purposely not implemented + void operator=(const Self&); //purposely not implemented + + unsigned int m_NumberOfCoils; +}; + +} // end namespace anima diff --git a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.h b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.h index cf29df8b2..3b6a40dc2 100644 --- a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.h +++ b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.h @@ -137,7 +137,7 @@ class MCMEstimatorImageFilter : std::vector< GradientType > &GetGradientDirections() {return m_GradientDirections;} MCMCreatorType *GetMCMCreator(unsigned int i) {return m_MCMCreators[i];} - virtual MCMCreatorType *GetNewMCMCreatorInstance(); + MCMCreatorType *GetNewMCMCreatorInstance(); // Output options OutputScalarImageType *GetAICcVolume () {return m_AICcVolume;} @@ -231,7 +231,7 @@ class MCMEstimatorImageFilter : void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; //! Create a cost function following the noise type and estimation mode - virtual CostFunctionBasePointer CreateCostFunction(std::vector &observedSignals, MCMPointer &mcmModel); + CostFunctionBasePointer CreateCostFunction(std::vector &observedSignals, MCMPointer &mcmModel); //! Create an optimizer following the optimizer type and estimation mode OptimizerPointer CreateOptimizer(CostFunctionBasePointer &cost, itk::Array &lowerBounds, itk::Array &upperBounds); diff --git a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx index d07b82acf..4525f07c3 100644 --- a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx +++ b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx @@ -729,47 +729,56 @@ MCMEstimatorImageFilter::CreateCostFunction(std { CostFunctionBasePointer returnCost; - if (m_NoiseType != Gaussian) - itkExceptionMacro("Cost function type not supported"); - - anima::BaseMCMCost::Pointer baseCost; - if (m_MLEstimationStrategy != VariableProjection) - { - anima::GaussianMCMCost::Pointer internalCost = anima::GaussianMCMCost::New(); - internalCost->SetMarginalEstimation(m_MLEstimationStrategy == Marginal); - baseCost = internalCost; - } - else - { - anima::GaussianMCMVariableProjectionCost::Pointer internalCost = anima::GaussianMCMVariableProjectionCost::New(); - baseCost = internalCost; - } - - baseCost->SetObservedSignals(observedSignals); - baseCost->SetGradients(m_GradientDirections); - baseCost->SetSmallDelta(m_SmallDelta); - baseCost->SetBigDelta(m_BigDelta); - baseCost->SetGradientStrengths(m_GradientStrengths); - baseCost->SetMCMStructure(mcmModel); - - if (m_Optimizer == "levenberg") - { - anima::MCMMultipleValuedCostFunction::Pointer tmpCost = - anima::MCMMultipleValuedCostFunction::New(); - - tmpCost->SetInternalCost(baseCost); - - returnCost = tmpCost; - } - else - { - anima::MCMSingleValuedCostFunction::Pointer tmpCost = - anima::MCMSingleValuedCostFunction::New(); - - tmpCost->SetInternalCost(baseCost); - - returnCost = tmpCost; - } + if (m_NoiseType != Gaussian) + itkExceptionMacro("Cost function type not supported"); + + anima::BaseMCMCost::Pointer baseCost; + if (m_MLEstimationStrategy != VariableProjection) + { + if (this->GetNoiseType() == NCC) + { + anima::NonCentralChiMCMCost::Pointer internalCost = anima::NonCentralChiMCMCost::New(); + internalCost->SetNumberOfCoils(this->GetNumberOfCoils()); + baseCost = internalCost; + } + else + { + anima::GaussianMCMCost::Pointer internalCost = anima::GaussianMCMCost::New(); + internalCost->SetMarginalEstimation(m_MLEstimationStrategy == Marginal); + baseCost = internalCost; + } + } + else + { + anima::GaussianMCMVariableProjectionCost::Pointer internalCost = anima::GaussianMCMVariableProjectionCost::New(); + baseCost = internalCost; + } + + baseCost->SetObservedSignals(observedSignals); + baseCost->SetGradients(m_GradientDirections); + baseCost->SetSmallDelta(m_SmallDelta); + baseCost->SetBigDelta(m_BigDelta); + baseCost->SetGradientStrengths(m_GradientStrengths); + baseCost->SetMCMStructure(mcmModel); + + if (m_Optimizer == "levenberg") + { + anima::MCMMultipleValuedCostFunction::Pointer tmpCost = + anima::MCMMultipleValuedCostFunction::New(); + + tmpCost->SetInternalCost(baseCost); + + returnCost = tmpCost; + } + else + { + anima::MCMSingleValuedCostFunction::Pointer tmpCost = + anima::MCMSingleValuedCostFunction::New(); + + tmpCost->SetInternalCost(baseCost); + + returnCost = tmpCost; + } return returnCost; } diff --git a/Anima/diffusion/mcm_estimator/low_memory/CMakeLists.txt b/Anima/diffusion/mcm_estimator/low_memory/CMakeLists.txt new file mode 100644 index 000000000..9247cbe01 --- /dev/null +++ b/Anima/diffusion/mcm_estimator/low_memory/CMakeLists.txt @@ -0,0 +1,43 @@ +if(BUILD_TOOLS AND USE_NLOPT) + +project(animaLowMemMCMEstimator) + +## ############################################################################# +## 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 + AnimaMCMBase + AnimaOptimizers + ${NLOPT_LIBRARY} + AnimaSpecialFunctions + ITKOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimator.cxx b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimator.cxx new file mode 100644 index 000000000..fa04bb41e --- /dev/null +++ b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimator.cxx @@ -0,0 +1,165 @@ +#include +#include + +#include "animaLowMemMCMEstimatorBridge.h" + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + // Required arguments + TCLAP::ValueArg dwiArg("i", "dwi-list", "DWI volume list", true, "", "DWI images", cmd); + TCLAP::ValueArg gradsArg("g", "grads", "Gradient table", true, "", "gradients", cmd); + TCLAP::ValueArg bvalsArg("b", "bvals", "B-value list", true, "", "b-values", cmd); + TCLAP::ValueArg inMoseArg("", "in-mose", "Input model selection map (overrides model selection step)", false, "", "model selection input image", cmd); + TCLAP::SwitchArg bvalueScaleArg("B","b-no-scale","Do not scale b-values according to gradient norm",cmd); + TCLAP::ValueArg smallDeltaArg("", "small-delta", "Diffusion small delta (in seconds)", false, anima::DiffusionSmallDelta, "small delta", cmd); + TCLAP::ValueArg bigDeltaArg("", "big-delta", "Diffusion big delta (in seconds)", false, anima::DiffusionBigDelta, "big delta", cmd); + + // Outputs + TCLAP::ValueArg outArg("o", "out-mcm", "MCM output prefix", true, "", "MCM output prefix", cmd); + TCLAP::ValueArg aicArg("a", "out-aic", "Output estimated AICu prefix", false, "", "AICu output prefix", cmd); + TCLAP::ValueArg outB0Arg("", "out-b0", "Output estimated B0 prefix", false, "", "B0 output prefix", cmd); + TCLAP::ValueArg outSigmaArg("", "out-sig", "Output estimated noise sigma square prefix", false, "", "noise sigma square output prefix", cmd); + TCLAP::ValueArg outMoseArg("", "out-mose", "Output model selection map prefix", false, "", "model selection output prefix", cmd); + + // Optional arguments + TCLAP::ValueArg computationMaskArg("m", "mask", "Computation mask", false, "", "computation mask", cmd); + TCLAP::ValueArg b0thrArg("", "b0-thr", "Background threshold on B0 value (default: 10)", false, 10.0, "B0 theshold", cmd); + + TCLAP::ValueArg nbFasciclesArg("n", "nb-fascicles", "Number of computed fascicles (default: 2)", false, 2, "number of fascicles", cmd); + TCLAP::ValueArg compartmentTypeArg("c", "comp-type", "Compartment type for fascicles: 1: stick, 2: zeppelin, 3: tensor, 4: NODDI, 5: DDI (default: 3)", false, 3, "fascicles type", cmd); + TCLAP::SwitchArg aicSelectNbCompartmentsArg("M", "opt-nb-comp", "Activate AICC-based number of compartments selection", cmd, false); + + TCLAP::SwitchArg freeWaterCompartmentArg("F", "free-water", "Model with free water", cmd, false); + TCLAP::SwitchArg stationaryWaterCompartmentArg("W", "stationary-water", "Model with stationary water", cmd, false); + TCLAP::SwitchArg restrictedWaterCompartmentArg("R", "restricted-water", "Model with restricted water", cmd, false); + TCLAP::SwitchArg staniszCompartmentArg("Z", "stanisz", "Model with stanisz isotropic compartment", cmd, false); + + TCLAP::SwitchArg optFWDiffArg("", "opt-free-water-diff", "Optimize free water diffusivity value", cmd, false); + TCLAP::SwitchArg optIRWDiffArg("", "opt-ir-water-diff", "Optimize isotropic restricted water diffusivity value", cmd, false); + TCLAP::SwitchArg optStaniszRadiusArg("", "opt-stanisz-radius", "Optimize isotropic Stanisz radius value", cmd, false); + TCLAP::SwitchArg optStaniszDiffArg("", "opt-stanisz-diff", "Optimize isotropic Stanisz diffusivity value", cmd, false); + + TCLAP::SwitchArg fixDiffArg("", "fix-diff", "Fix diffusivity value", cmd, false); + TCLAP::SwitchArg fixKappaArg("", "fix-kappa", "Fix orientation concentration values", cmd, false); + TCLAP::SwitchArg fixEAFArg("", "fix-eaf", "Fix extra axonal fraction values", cmd, false); + + TCLAP::SwitchArg commonDiffusivitiesArg("", "common-diffusivities", "Share diffusivity values among compartments", cmd, false); + TCLAP::SwitchArg commonKappaArg("", "common-kappa", "Share orientation concentration values among compartments", cmd, false); + TCLAP::SwitchArg commonEAFArg("", "common-eaf", "Share extra axonal fraction values among compartments", cmd, false); + + //Initial values for diffusivities + TCLAP::ValueArg initAxialDiffArg("", "init-axial-diff", "Initial axial diffusivity (default: 1.71e-3)", false, 1.71e-3, "initial axial diffusivity", cmd); + TCLAP::ValueArg initRadialDiff1Arg("", "init-radial-diff1", "Initial first radial diffusivity (default: 1.9e-4)", false, 1.9e-4, "initial first radial diffusivity", cmd); + TCLAP::ValueArg initRadialDiff2Arg("", "init-radial-diff2", "Initial second radial diffusivity (default: 1.5e-4)", false, 1.5e-4, "initial second radial diffusivity", cmd); + TCLAP::ValueArg initIRWDiffArg("", "init-irw-diff", "Initial isotropic restricted diffusivity (default: 7.5e-4)", false, 7.5e-4, "initial IRW diffusivity", cmd); + TCLAP::ValueArg initStaniszDiffArg("", "init-stanisz-diff", "Initial Stanisz diffusivity (default: 1.71e-3)", false, 1.71e-3, "initial Stanisz diffusivity", cmd); + + // Optimization parameters + TCLAP::ValueArg optimizerArg("", "optimizer", "Optimizer for estimation: bobyqa (default), ccsaq, bfgs or levenberg", false, "bobyqa", "optimizer", cmd); + TCLAP::ValueArg absCostChangeArg("", "abs-cost-change", "Cost function change to stop estimation (default: 0.01)", false, 0.01, "cost change threshold", cmd); + TCLAP::ValueArg mlModeArg("", "ml-mode", "ML estimation strategy: marginal likelihood (0), profile likelihood (1, default), Variable projection (2)", false, 1, "ML mode", cmd); + TCLAP::ValueArg xTolArg("x", "x-tol", "Tolerance for position in optimization (default: 0 -> 1.0e-4 or 1.0e-7 for bobyqa)", false, 0, "position tolerance", cmd); + TCLAP::ValueArg fTolArg("", "f-tol", "Tolerance for relative cost in optimization (default: 0 -> function of position tolerance)", false, 0, "cost relative tolerance", cmd); + TCLAP::ValueArg maxEvalArg("e", "max-eval", "Maximum evaluations (default: 0 -> function of number of unknowns)", false, 0, "max evaluations", cmd); + + TCLAP::ValueArg noiseTypeArg("", "noise-type", "Noise type for optimization: 0: Gaussian (default) or 1: NCC", false, 0, "noise type", cmd); + TCLAP::ValueArg nbCoilsArg("", "nb-coils", "Number of coils, for NCC estimation (default: 1)", false, 1, "number of coils", cmd); + + TCLAP::ValueArg nbThreadsArg("T", "nb-threads", "Number of threads to run on (default: all cores)", false, itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(), "number of threads", cmd); + + TCLAP::ValueArg splitsArg("s","split","Split image for low memory (default: 2)",false,2,"Number of splits",cmd); + TCLAP::ValueArg specSplitArg("S","splittoprocess","Specific split to process (use to run on cluster (default: -1 = all)",false,-1,"Split to process",cmd); + TCLAP::SwitchArg genOutputDescroArg("G","generateouputdescription","Generate ouptut description data",cmd,false); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + typedef anima::LowMemMCMEstimatorBridge EstimatorBridgeType; + + EstimatorBridgeType *mainFilter = new EstimatorBridgeType; + + mainFilter->SetComputationMask(computationMaskArg.getValue()); + mainFilter->SetNumberOfThreads(nbThreadsArg.getValue()); + + mainFilter->SetDWIFileNames(dwiArg.getValue()); + if (inMoseArg.getValue() != "") + mainFilter->SetInputMoseName(inMoseArg.getValue()); + + mainFilter->SetOutputName(outArg.getValue()); + mainFilter->SetNbSplits(splitsArg.getValue()); + + mainFilter->SetGradients(gradsArg.getValue()); + mainFilter->SetBValues(bvalsArg.getValue()); + mainFilter->SetBValueScale(bvalueScaleArg.getValue()); + + mainFilter->SetOutputAICName(aicArg.getValue()); + mainFilter->SetOutputB0Name(outB0Arg.getValue()); + mainFilter->SetOutputSigmaName(outSigmaArg.getValue()); + mainFilter->SetOutputMoseName(outMoseArg.getValue()); + + mainFilter->SetB0Threshold(b0thrArg.getValue()); + mainFilter->SetNumberOfFascicles(nbFasciclesArg.getValue()); + mainFilter->SetFindOptimalNumberOfCompartments(aicSelectNbCompartmentsArg.isSet()); + mainFilter->SetCompartmentType(compartmentTypeArg.getValue()); + + mainFilter->SetFreeWaterCompartment(freeWaterCompartmentArg.isSet()); + mainFilter->SetStationaryWaterCompartment(stationaryWaterCompartmentArg.isSet()); + mainFilter->SetRestrictedWaterCompartment(restrictedWaterCompartmentArg.isSet()); + mainFilter->SetStaniszCompartment(staniszCompartmentArg.isSet()); + + mainFilter->SetAxialDiffusivityValue(initAxialDiffArg.getValue()); + mainFilter->SetRadialDiffusivity1Value(initRadialDiff1Arg.getValue()); + mainFilter->SetRadialDiffusivity2Value(initRadialDiff2Arg.getValue()); + mainFilter->SetIRWDiffusivityValue(initIRWDiffArg.getValue()); + mainFilter->SetStaniszDiffusivityValue(initStaniszDiffArg.getValue()); + + mainFilter->SetOptimizeFreeWaterDiffusivity(optFWDiffArg.isSet()); + mainFilter->SetOptimizeIRWDiffusivity(optIRWDiffArg.isSet()); + mainFilter->SetOptimizeStaniszDiffusivity(optStaniszDiffArg.isSet()); + mainFilter->SetOptimizeStaniszRadius(optStaniszRadiusArg.isSet()); + + mainFilter->SetFixDiffusivity(fixDiffArg.isSet()); + mainFilter->SetFixKappa(fixKappaArg.isSet()); + mainFilter->SetFixEAF(fixEAFArg.isSet()); + + mainFilter->SetCommonDiffusivities(commonDiffusivitiesArg.isSet()); + mainFilter->SetCommonKappa(commonKappaArg.isSet()); + mainFilter->SetCommonEAF(commonEAFArg.isSet()); + + mainFilter->SetOptimizerType(optimizerArg.getValue()); + mainFilter->SetAbsCostChange(absCostChangeArg.getValue()); + + mainFilter->SetXTolerance(xTolArg.getValue()); + mainFilter->SetFTolerance(fTolArg.getValue()); + mainFilter->SetMaxEval(maxEvalArg.getValue()); + + mainFilter->SetNoiseType((EstimatorBridgeType::SignalNoiseType)noiseTypeArg.getValue()); + mainFilter->SetNumberOfCoils(nbCoilsArg.getValue()); + mainFilter->SetMLEstimationStrategy((EstimatorBridgeType::MaximumLikelihoodEstimationMode)mlModeArg.getValue()); + + mainFilter->SetSmallDelta(smallDeltaArg.getValue()); + mainFilter->SetBigDelta(bigDeltaArg.getValue()); + + try + { + mainFilter->Update(specSplitArg.getValue(),genOutputDescroArg.getValue()); + } + catch(itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + delete mainFilter; + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.cxx b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.cxx new file mode 100644 index 000000000..fabcb29e6 --- /dev/null +++ b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.cxx @@ -0,0 +1,384 @@ +#include "animaLowMemMCMEstimatorBridge.h" +#include +#include +#include +#include + +namespace anima +{ + +LowMemMCMEstimatorBridge::LowMemMCMEstimatorBridge() +{ + m_NbSplits = 2; + m_NumThreads = itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(); + + m_DWIImages = new ImageSplitterType; + m_InputMoseImage = ITK_NULLPTR; + m_ComputationMask = ITK_NULLPTR; + + m_SmallDelta = anima::DiffusionSmallDelta; + m_BigDelta = anima::DiffusionBigDelta; + + m_AxialDiffusivityValue = 1.71e-3; + m_StaniszDiffusivityValue = 1.71e-3; + m_IRWDiffusivityValue = 7.5e-4; + m_RadialDiffusivity1Value = 1.9e-4; + m_RadialDiffusivity2Value = 1.5e-4; +} + +LowMemMCMEstimatorBridge::~LowMemMCMEstimatorBridge() +{ + if (m_DWIImages) + delete m_DWIImages; + if (m_InputMoseImage) + delete m_InputMoseImage; +} + +void LowMemMCMEstimatorBridge::SetComputationMask(std::string &cMask) +{ + m_ComputationMask = anima::readImage (cMask); + m_DWIImages->SetComputationMask(m_ComputationMask); + if (m_InputMoseImage) + m_InputMoseImage->SetComputationMask(m_ComputationMask); +} + +void LowMemMCMEstimatorBridge::SetInputMoseName(std::string &fileName) +{ + if (!m_InputMoseImage) + m_InputMoseImage = new MoseImageSplitterType; + + m_InputMoseImage->SetUniqueFileName(fileName); + if (m_ComputationMask) + m_InputMoseImage->SetComputationMask(m_ComputationMask); +} + +void LowMemMCMEstimatorBridge::Update(int specificSplitToDo, bool genOutputDescriptionData) +{ + if (!m_ComputationMask) + itkExceptionMacro("No computation mask... Exiting..."); + + ImageSplitterType::TInputIndexType tmpInd; + for (unsigned int i = 0;i < MaskImageType::GetImageDimension();++i) + tmpInd[i] = m_NbSplits; + + m_DWIImages->SetNumberOfBlocks(tmpInd); + if (m_InputMoseImage) + m_InputMoseImage->SetNumberOfBlocks(tmpInd); + + std::vector < ImageSplitterType::TInputIndexType > splitIndexesToProcess; + + if (specificSplitToDo != -1) + { + tmpInd[0] = (unsigned int)floor((double)(specificSplitToDo/(m_NbSplits*m_NbSplits))); + unsigned int tmpVal = specificSplitToDo - tmpInd[0]*m_NbSplits*m_NbSplits; + tmpInd[1] = (unsigned int)floor((double)(tmpVal/m_NbSplits)); + tmpInd[2] = tmpVal - tmpInd[1]*m_NbSplits; + + if (!m_DWIImages->EmptyMask(tmpInd)) + splitIndexesToProcess.push_back(tmpInd); + } + else + { + for (unsigned int i = 0;i < m_NbSplits;++i) + { + tmpInd[0] = i; + for (unsigned int j = 0;j < m_NbSplits;++j) + { + tmpInd[1] = j; + for (unsigned int k = 0;k < m_NbSplits;++k) + { + tmpInd[2] = k; + + if (!m_DWIImages->EmptyMask(tmpInd)) + splitIndexesToProcess.push_back(tmpInd); + } + } + } + } + + for (unsigned int i = 0;i < splitIndexesToProcess.size();++i) + { + std::cout << "Processing block : " << splitIndexesToProcess[i][0] << " " + << splitIndexesToProcess[i][1] << " " << splitIndexesToProcess[i][2] << std::endl; + + m_DWIImages->SetBlockIndex(splitIndexesToProcess[i]); + m_DWIImages->Update(); + + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + + BaseFilterType::Pointer filter = BaseFilterType::New(); + + filter->SetUseConstrainedOrientationConcentration(m_FixKappa); + if (!m_FixKappa) + filter->SetUseCommonConcentrations(m_CommonKappa); + else + filter->SetUseCommonConcentrations(false); + + filter->SetUseConstrainedExtraAxonalFraction(m_FixEAF); + if (!m_FixEAF) + filter->SetUseCommonExtraAxonalFractions(m_CommonEAF); + else + filter->SetUseCommonExtraAxonalFractions(false); + + for (unsigned int j = 0;j < m_DWIImages->GetNbImages();++j) + filter->SetInput(j,m_DWIImages->GetOutput(j)); + + // Load gradient table and b-value list + std::cout << "Importing gradient table and b-values..." << std::endl; + + typedef anima::GradientFileReader < vnl_vector_fixed, double > GFReaderType; + GFReaderType gfReader; + gfReader.SetGradientFileName(m_Gradients); + gfReader.SetBValueBaseString(m_BValues); + gfReader.SetGradientIndependentNormalization(m_BValueScale); + gfReader.SetSmallDelta(m_SmallDelta); + gfReader.SetBigDelta(m_BigDelta); + gfReader.Update(); + + GFReaderType::GradientVectorType directions = gfReader.GetGradients(); + for(unsigned int j = 0;j < directions.size();++j) + filter->AddGradientDirection(j,directions[j]); + + GFReaderType::BValueVectorType mb = gfReader.GetGradientStrengths(); + + filter->SetGradientStrengths(mb); + filter->SetSmallDelta(m_SmallDelta); + filter->SetBigDelta(m_BigDelta); + + if (m_InputMoseImage) + { + m_InputMoseImage->SetBlockIndex(splitIndexesToProcess[i]); + m_InputMoseImage->Update(); + filter->SetMoseVolume(m_InputMoseImage->GetOutput(0)); + } + + filter->SetComputationMask(m_DWIImages->GetSmallMaskWithMargin()); + + filter->SetB0Threshold(m_B0Threshold); + + filter->SetModelWithFreeWaterComponent(m_FreeWaterCompartment); + filter->SetModelWithStationaryWaterComponent(m_StationaryWaterCompartment); + filter->SetModelWithRestrictedWaterComponent(m_RestrictedWaterCompartment); + filter->SetModelWithStaniszComponent(m_StaniszCompartment); + + switch (m_CompartmentType) + { + case 1: + filter->SetCompartmentType(anima::Stick); + break; + + case 2: + filter->SetCompartmentType(anima::Zeppelin); + break; + + case 3: + filter->SetCompartmentType(anima::Tensor); + break; + + case 4: + filter->SetCompartmentType(anima::NODDI); + break; + + case 5: + filter->SetCompartmentType(anima::DDI); + break; + + default: + itkExceptionMacro("Unsupported compartment type"); + } + + filter->SetAxialDiffusivityValue(m_AxialDiffusivityValue); + filter->SetRadialDiffusivity1Value(m_RadialDiffusivity1Value); + filter->SetRadialDiffusivity2Value(m_RadialDiffusivity2Value); + filter->SetIRWDiffusivityValue(m_IRWDiffusivityValue); + filter->SetStaniszDiffusivityValue(m_StaniszDiffusivityValue); + + filter->SetNumberOfCompartments(m_NumberOfFascicles); + + filter->SetOptimizer(m_OptimizerType); + filter->SetAbsoluteCostChange(m_AbsCostChange); + filter->SetXTolerance(m_XTolerance); + filter->SetFTolerance(m_FTolerance); + filter->SetMaxEval(m_MaxEval); + filter->SetFindOptimalNumberOfCompartments(m_FindOptimalNumberOfCompartments); + filter->SetMLEstimationStrategy(m_MLEstimationStrategy); + + filter->SetNoiseType(m_NoiseType); + filter->SetNumberOfCoils(m_NumberOfCoils); + + filter->SetUseConstrainedDiffusivity(m_FixDiffusivity); + filter->SetUseConstrainedFreeWaterDiffusivity(!m_OptimizeFreeWaterDiffusivity); + filter->SetUseConstrainedIRWDiffusivity(!m_OptimizeIRWDiffusivity); + filter->SetUseConstrainedStaniszDiffusivity(!m_OptimizeStaniszDiffusivity); + filter->SetUseConstrainedStaniszRadius(!m_OptimizeStaniszRadius); + + if (!m_FixDiffusivity) + filter->SetUseCommonDiffusivities(m_CommonDiffusivities); + else + filter->SetUseCommonDiffusivities(false); + + filter->SetNumberOfWorkUnits(m_NumThreads); + filter->AddObserver(itk::ProgressEvent(), callback); + + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + filter->Update(); + tmpTimer.Stop(); + + std::cout << "Results computed in " << tmpTimer.GetTotal() << "s ... Writing output parcel..." << std::endl; + + char numSplitMCM[2048]; + sprintf(numSplitMCM,"_%ld_%ld_%ld.mcm",splitIndexesToProcess[i][0],splitIndexesToProcess[i][1],splitIndexesToProcess[i][2]); + char numSplit[2048]; + sprintf(numSplit,"_%ld_%ld_%ld.nrrd",splitIndexesToProcess[i][0],splitIndexesToProcess[i][1],splitIndexesToProcess[i][2]); + + //Write outputs + std::string outputName = m_OutputName + numSplitMCM; + std::string outputAICName = m_OutputAICName + numSplit; + std::string outputB0Name = m_OutputB0Name + numSplit; + std::string outputSigmaName = m_OutputSigmaName + numSplit; + std::string outputMoseName = m_OutputMoseName + numSplit; + + this->BuildAndWriteMCM(filter->GetOutput(),outputName,m_DWIImages->GetBlockRegionInsideMargin()); + + if (m_OutputAICName != "") + this->BuildAndWriteAdditional(filter->GetAICcVolume(),outputAICName,m_DWIImages->GetBlockRegionInsideMargin()); + + if (m_OutputB0Name != "") + this->BuildAndWriteAdditional(filter->GetB0Volume(),outputB0Name,m_DWIImages->GetBlockRegionInsideMargin()); + + if (m_OutputSigmaName != "") + this->BuildAndWriteAdditional(filter->GetSigmaSquareVolume(),outputSigmaName,m_DWIImages->GetBlockRegionInsideMargin()); + + if (m_OutputMoseName != "") + this->BuildAndWriteAdditional(filter->GetMoseVolume(),outputMoseName,m_DWIImages->GetBlockRegionInsideMargin()); + } + + if (genOutputDescriptionData) + { + std::string tmpOutName = m_OutputName + ".txt"; + std::ofstream tmpFileOut(tmpOutName.c_str()); + + std::ofstream tmpFileAICOut; + std::ofstream tmpFileB0Out; + std::ofstream tmpFileSigmaOut; + + if (m_OutputAICName != "") + { + std::string tmpOutAICName = m_OutputAICName + ".txt"; + tmpFileAICOut.open(tmpOutAICName.c_str()); + } + + if (m_OutputB0Name != "") + { + std::string tmpOutB0Name = m_OutputB0Name + ".txt"; + tmpFileB0Out.open(tmpOutB0Name.c_str()); + } + + if (m_OutputSigmaName != "") + { + std::string tmpOutSigmaName = m_OutputSigmaName + ".txt"; + tmpFileSigmaOut.open(tmpOutSigmaName.c_str()); + } + + for (unsigned int i = 0;i < m_NbSplits;++i) + { + tmpInd[0] = i; + for (unsigned int j = 0;j < m_NbSplits;++j) + { + tmpInd[1] = j; + for (unsigned int k = 0;k < m_NbSplits;++k) + { + tmpInd[2] = k; + + if (!m_DWIImages->EmptyMask(tmpInd)) + { + OutputImageType::RegionType tmpBlRegion = m_DWIImages->GetSpecificBlockRegion(tmpInd); + char numSplitMCM[2048]; + sprintf(numSplitMCM,"_%ld_%ld_%ld.mcm",tmpInd[0],tmpInd[1],tmpInd[2]); + char numSplit[2048]; + sprintf(numSplit,"_%ld_%ld_%ld.nrrd",tmpInd[0],tmpInd[1],tmpInd[2]); + + tmpFileOut << "" << std::endl; + tmpFileOut << "BLOCK_FILE=" << m_OutputName + numSplitMCM << std::endl; + tmpFileOut << "STARTING_INDEX=" << tmpBlRegion.GetIndex()[0] << " " + << tmpBlRegion.GetIndex()[1] << " " << tmpBlRegion.GetIndex()[2] << std::endl; + tmpFileOut << "" << std::endl; + + if (tmpFileAICOut.is_open()) + { + tmpFileAICOut << "" << std::endl; + tmpFileAICOut << "BLOCK_FILE=" << m_OutputAICName + numSplit << std::endl; + tmpFileAICOut << "STARTING_INDEX=" << tmpBlRegion.GetIndex()[0] << " " + << tmpBlRegion.GetIndex()[1] << " " << tmpBlRegion.GetIndex()[2] << std::endl; + tmpFileAICOut << "" << std::endl; + } + + if (tmpFileB0Out.is_open()) + { + tmpFileB0Out << "" << std::endl; + tmpFileB0Out << "BLOCK_FILE=" << m_OutputB0Name + numSplit << std::endl; + tmpFileB0Out << "STARTING_INDEX=" << tmpBlRegion.GetIndex()[0] << " " + << tmpBlRegion.GetIndex()[1] << " " << tmpBlRegion.GetIndex()[2] << std::endl; + tmpFileB0Out << "" << std::endl; + } + + if (tmpFileSigmaOut.is_open()) + { + tmpFileSigmaOut << "" << std::endl; + tmpFileSigmaOut << "BLOCK_FILE=" << m_OutputSigmaName + numSplit << std::endl; + tmpFileSigmaOut << "STARTING_INDEX=" << tmpBlRegion.GetIndex()[0] << " " + << tmpBlRegion.GetIndex()[1] << " " << tmpBlRegion.GetIndex()[2] << std::endl; + tmpFileSigmaOut << "" << std::endl; + } + } + } + } + } + + tmpFileOut.close(); + tmpFileAICOut.close(); + tmpFileB0Out.close(); + tmpFileSigmaOut.close(); + } +} + +void LowMemMCMEstimatorBridge::BuildAndWriteMCM(OutputImageType *tmpIm, std::string resName, + OutputImageType::RegionType finalROI) +{ + OutputImageType::RegionType tmpRegion = finalROI; + for (unsigned int i = 0;i < OutputImageType::GetImageDimension();++i) + tmpRegion.SetIndex(i,0); + + OutputImageType::Pointer tmpRes = OutputImageType::New(); + tmpRes->Initialize(); + + tmpRes->SetOrigin(m_ComputationMask->GetOrigin()); + tmpRes->SetRegions(tmpRegion); + tmpRes->SetDirection(m_ComputationMask->GetDirection()); + tmpRes->SetSpacing(m_ComputationMask->GetSpacing()); + tmpRes->SetNumberOfComponentsPerPixel(tmpIm->GetNumberOfComponentsPerPixel()); + tmpRes->SetDescriptionModel(tmpIm->GetDescriptionModel()); + + tmpRes->Allocate(); + + itk::ImageRegionIterator tmpImIt (tmpIm,finalROI); + itk::ImageRegionIterator tmpResIt (tmpRes,tmpRegion); + + while (!tmpImIt.IsAtEnd()) + { + tmpResIt.Set(tmpImIt.Get()); + + ++tmpImIt; + ++tmpResIt; + } + + anima::MCMFileWriter mcmWriter; + mcmWriter.SetFileName(resName); + mcmWriter.SetInputImage(tmpRes); + mcmWriter.Update(); +} + +} // end namesapce anima diff --git a/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.h b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.h new file mode 100644 index 000000000..84f436003 --- /dev/null +++ b/Anima/diffusion/mcm_estimator/low_memory/animaLowMemMCMEstimatorBridge.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include + +namespace anima +{ + +class LowMemMCMEstimatorBridge +{ +public: + typedef anima::MCMEstimatorImageFilter BaseFilterType; + typedef BaseFilterType::InputImageType InputImageType; + typedef BaseFilterType::OutputImageType OutputImageType; + typedef BaseFilterType::OutputScalarImageType OutputScalarImageType; + typedef BaseFilterType::MCMType MCMType; + typedef BaseFilterType::SignalNoiseType SignalNoiseType; + typedef BaseFilterType::MaximumLikelihoodEstimationMode MaximumLikelihoodEstimationMode; + + typedef anima::ImageDataSplitter < InputImageType > ImageSplitterType; + typedef itk::Image MaskImageType; + typedef anima::ImageDataSplitter < MaskImageType > MoseImageSplitterType; + + LowMemMCMEstimatorBridge(); + ~LowMemMCMEstimatorBridge(); + + std::string GetNameOfClass() {return "LowMemMCMEstimatorBridge";} + + void SetComputationMask(std::string &cMask); + + void SetInputMoseName(std::string &fileName); + + void SetDWIFileNames(std::string &fileList) + { + m_DWIImages->SetFileNames(fileList); + } + + void SetOutputName(std::string &pref) {m_OutputName = pref;} + + void SetNbSplits(unsigned int nbSplits) {m_NbSplits = nbSplits;} + void SetNumberOfThreads(unsigned int &nbT) {m_NumThreads = nbT;} + + void Update(int specificSplitToDo = -1, bool genOutputDescriptionData = false); + + void SetGradients(std::string grads) {m_Gradients = grads;} + void SetBValues(std::string bvals) {m_BValues = bvals;} + void SetBValueScale(bool val) {m_BValueScale = val;} + + void SetOutputAICName(std::string aicName) {m_OutputAICName = aicName;} + void SetOutputB0Name(std::string b0Name) {m_OutputB0Name = b0Name;} + void SetOutputSigmaName(std::string sigmaName) {m_OutputSigmaName = sigmaName;} + void SetOutputMoseName(std::string moseName) {m_OutputMoseName = moseName;} + + void SetB0Threshold(double b0Thr) {m_B0Threshold = b0Thr;} + void SetNumberOfFascicles(unsigned int nbFasc) {m_NumberOfFascicles = nbFasc;} + void SetCompartmentType(unsigned int cType) {m_CompartmentType = cType;} + + void SetFreeWaterCompartment(bool fwComp) {m_FreeWaterCompartment = fwComp;} + void SetStationaryWaterCompartment(bool swComp) {m_StationaryWaterCompartment = swComp;} + void SetRestrictedWaterCompartment(bool irwComp) {m_RestrictedWaterCompartment = irwComp;} + void SetStaniszCompartment(bool zComp) {m_StaniszCompartment = zComp;} + + void SetOptimizeFreeWaterDiffusivity(bool opt) {m_OptimizeFreeWaterDiffusivity = opt;} + void SetOptimizeIRWDiffusivity(bool fix) {m_OptimizeIRWDiffusivity = fix;} + void SetOptimizeStaniszDiffusivity(bool fix) {m_OptimizeStaniszDiffusivity = fix;} + void SetOptimizeStaniszRadius(bool fix) {m_OptimizeStaniszRadius = fix;} + + void SetFixDiffusivity(bool fix) {m_FixDiffusivity = fix;} + void SetFixKappa(bool fix) {m_FixKappa = fix;} + void SetFixEAF(bool fix) {m_FixEAF = fix;} + + void SetAxialDiffusivityValue(double val) {m_AxialDiffusivityValue = val;} + void SetRadialDiffusivity1Value(double val) {m_RadialDiffusivity1Value = val;} + void SetRadialDiffusivity2Value(double val) {m_RadialDiffusivity2Value = val;} + void SetIRWDiffusivityValue(double val) {m_IRWDiffusivityValue = val;} + void SetStaniszDiffusivityValue(double val) {m_StaniszDiffusivityValue = val;} + + void SetCommonDiffusivities(bool common) {m_CommonDiffusivities = common;} + void SetCommonKappa(bool common) {m_CommonKappa = common;} + void SetCommonEAF(bool common) {m_CommonEAF = common;} + + void SetOptimizerType(std::string optType) {m_OptimizerType = optType;} + void SetAbsCostChange(double num) {m_AbsCostChange = num;} + void SetXTolerance(double num) {m_XTolerance = num;} + void SetFTolerance(double num) {m_FTolerance = num;} + void SetMaxEval(unsigned int num) {m_MaxEval = num;} + void SetFindOptimalNumberOfCompartments(bool val) {m_FindOptimalNumberOfCompartments = val;} + void SetMLEstimationStrategy(MaximumLikelihoodEstimationMode val) {m_MLEstimationStrategy = val;} + + void SetSmallDelta(double val) {m_SmallDelta = val;} + void SetBigDelta(double val) {m_BigDelta = val;} + + void SetNoiseType(SignalNoiseType val) {m_NoiseType = val;} + void SetNumberOfCoils(unsigned int val) {m_NumberOfCoils = val;} + + //Update progression of the process + static void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) + { + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< + void BuildAndWriteAdditional(TOutputType *tmpIm, std::string resName, typename TOutputType::RegionType finalROI) + { + typename TOutputType::RegionType tmpRegion = finalROI; + for (unsigned int i = 0;i < TOutputType::GetImageDimension();++i) + tmpRegion.SetIndex(i,0); + + typename TOutputType::Pointer tmpRes = TOutputType::New(); + tmpRes->Initialize(); + + tmpRes->SetOrigin(m_ComputationMask->GetOrigin()); + tmpRes->SetRegions(tmpRegion); + tmpRes->SetDirection(m_ComputationMask->GetDirection()); + tmpRes->SetSpacing(m_ComputationMask->GetSpacing()); + + tmpRes->Allocate(); + + itk::ImageRegionIterator tmpImIt (tmpIm,finalROI); + itk::ImageRegionIterator tmpResIt (tmpRes,tmpRegion); + + while (!tmpImIt.IsAtEnd()) + { + tmpResIt.Set(tmpImIt.Get()); + + ++tmpImIt; + ++tmpResIt; + } + + anima::writeImage (resName,tmpRes); + } + +private: + std::string m_OutputName; + + unsigned int m_NbSplits; + unsigned int m_NumThreads; + + ImageSplitterType *m_DWIImages; + MoseImageSplitterType *m_InputMoseImage; + MaskImageType::Pointer m_ComputationMask; + + std::string m_Gradients, m_BValues; + bool m_BValueScale; + std::string m_OutputAICName, m_OutputB0Name, m_OutputSigmaName, m_OutputMoseName; + + double m_B0Threshold; + unsigned int m_NumberOfFascicles, m_CompartmentType; + + bool m_FreeWaterCompartment, m_StationaryWaterCompartment, m_RestrictedWaterCompartment, m_StaniszCompartment; + MaximumLikelihoodEstimationMode m_MLEstimationStrategy; + bool m_FindOptimalNumberOfCompartments; + + bool m_FixDiffusivity, m_OptimizeFreeWaterDiffusivity, m_OptimizeIRWDiffusivity, m_FixKappa, m_FixEAF; + bool m_OptimizeStaniszDiffusivity, m_OptimizeStaniszRadius; + bool m_CommonDiffusivities, m_CommonKappa, m_CommonEAF; + + double m_AxialDiffusivityValue; + double m_RadialDiffusivity1Value, m_RadialDiffusivity2Value; + double m_IRWDiffusivityValue; + double m_StaniszDiffusivityValue; + + std::string m_OptimizerType; + double m_AbsCostChange; + double m_XTolerance, m_FTolerance; + unsigned int m_MaxEval; + + double m_SmallDelta; + double m_BigDelta; + + SignalNoiseType m_NoiseType; + unsigned int m_NumberOfCoils; +}; + +} // end namespace anima diff --git a/Anima/diffusion/mcm_model_averaging/CMakeLists.txt b/Anima/diffusion/mcm_model_averaging/CMakeLists.txt new file mode 100644 index 000000000..73714f79c --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/CMakeLists.txt @@ -0,0 +1,41 @@ +if(BUILD_TOOLS) + +project(animaMCMModelAveraging) + +## ############################################################################# +## 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 + AnimaMCMBase + AnimaOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.h b/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.h new file mode 100644 index 000000000..70bfcfae6 --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.h @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace anima +{ + +template +class MCMImageSimplifier : +public anima::NumberedThreadImageToImageFilter < anima::MCMImage, anima::MCMImage > +{ +public: + /** Standard class typedefs. */ + typedef MCMImageSimplifier Self; + typedef anima::MCMImage InputImageType; + typedef anima::MCMImage OutputImageType; + typedef itk::Image MoseImageType; + typedef anima::NumberedThreadImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + typedef anima::MultiCompartmentModel MCModelType; + typedef typename MCModelType::Pointer MCModelPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Run-time type information (and related methods) */ + itkTypeMacro(MCMImageSimplifier, anima::NumberedThreadImageToImageFilter) + + /** Image typedef support */ + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + typedef typename OutputImageType::PixelType OutputPixelType; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + void SetMoseVolume(MoseImageType *vol) {m_MoseMap = vol;} + +protected: + MCMImageSimplifier() + : Superclass() + { + m_MoseMap = 0; + } + + virtual ~MCMImageSimplifier() {} + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + void InitializeReferenceOutputModel(); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(MCMImageSimplifier); + + MoseImageType::Pointer m_MoseMap; + MCModelPointer m_ReferenceOutputModel; +}; + +} // end of namespace anima + +#include "animaMCMImageSimplifier.hxx" diff --git a/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.hxx b/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.hxx new file mode 100644 index 000000000..3597b6db8 --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/animaMCMImageSimplifier.hxx @@ -0,0 +1,161 @@ +#pragma once +#include "animaMCMImageSimplifier.h" + +#include +#include + +namespace anima +{ + +template +void +MCMImageSimplifier +::GenerateOutputInformation() +{ + // Override the method in itkImageSource, so we can set the vector length of + // the output itk::VectorImage + + this->Superclass::GenerateOutputInformation(); + this->InitializeReferenceOutputModel(); + + OutputImageType *output = this->GetOutput(); + output->SetVectorLength(m_ReferenceOutputModel->GetSize()); +} + +template +void +MCMImageSimplifier +::InitializeReferenceOutputModel() +{ + // We assume that all non free water compartments are of the same type + bool modelWithIRW = false; + bool modelWithSW = false; + bool modelWithFW = false; + bool modelWithStanisz = false; + + InputImageType *inImage = const_cast (this->GetInput()); + unsigned int numIsotropicCompartments = inImage->GetDescriptionModel()->GetNumberOfIsotropicCompartments(); + for (unsigned int j = 0;j < numIsotropicCompartments;++j) + { + anima::DiffusionModelCompartmentType compIsoType = inImage->GetDescriptionModel()->GetCompartment(j)->GetCompartmentType(); + + switch (compIsoType) + { + case anima::FreeWater: + modelWithFW = true; + break; + case anima::StationaryWater: + modelWithSW = true; + break; + case anima::Stanisz: + modelWithStanisz = true; + break; + case anima::IsotropicRestrictedWater: + default: + modelWithIRW = true; + break; + } + } + + anima::DiffusionModelCompartmentType compartmentType; + if (inImage->GetDescriptionModel()->GetNumberOfCompartments() > numIsotropicCompartments) + compartmentType = inImage->GetDescriptionModel()->GetCompartment(numIsotropicCompartments)->GetCompartmentType(); + else + itkExceptionMacro("Only isotropic compartments, nothing to do here.") + + unsigned int maxCompartments = 0; + itk::ImageRegionConstIterator moseItr(m_MoseMap,m_MoseMap->GetLargestPossibleRegion()); + + while (!moseItr.IsAtEnd()) + { + unsigned int moseVal = moseItr.Get(); + if (moseVal > maxCompartments) + maxCompartments = moseVal; + + ++moseItr; + } + + typedef anima::MultiCompartmentModelCreator MCMCreatorType; + MCMCreatorType mcmCreator; + mcmCreator.SetModelWithFreeWaterComponent(modelWithFW); + mcmCreator.SetModelWithRestrictedWaterComponent(modelWithIRW); + mcmCreator.SetModelWithStationaryWaterComponent(modelWithSW); + mcmCreator.SetModelWithStaniszComponent(modelWithStanisz); + mcmCreator.SetCompartmentType(compartmentType); + mcmCreator.SetNumberOfCompartments(maxCompartments); + + m_ReferenceOutputModel = mcmCreator.GetNewMultiCompartmentModel(); + this->GetOutput()->SetDescriptionModel(m_ReferenceOutputModel); +} + +template +void +MCMImageSimplifier +::BeforeThreadedGenerateData() +{ + Superclass::BeforeThreadedGenerateData(); + + if (!m_MoseMap) + itkExceptionMacro("Mose map required to simplify"); + + if (!m_ReferenceOutputModel) + this->InitializeReferenceOutputModel(); +} + +template +void +MCMImageSimplifier +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + // Iterator definitions + typedef itk::ImageRegionIterator ImageIteratorType; + + InputImageType *inImage = const_cast (this->GetInput()); + ImageIteratorType inItr(inImage,this->GetInput()->GetLargestPossibleRegion()); + ImageIteratorType outItr(this->GetOutput(),this->GetInput()->GetLargestPossibleRegion()); + + MCModelPointer inputModel = inImage->GetDescriptionModel()->Clone(); + MCModelPointer outputModel = m_ReferenceOutputModel->Clone(); + + OutputPixelType inputVector, outputVector; + + MCModelType::ListType weights(outputModel->GetNumberOfCompartments()); + unsigned int numIsoCompartments = outputModel->GetNumberOfIsotropicCompartments(); + + while (!outItr.IsAtEnd()) + { + inputVector = inItr.Get(); + + inputModel->SetModelVector(inputVector); + std::fill(weights.begin(),weights.end(),0.0); + + for (unsigned int i = 0;i < numIsoCompartments;++i) + { + weights[i] = inputModel->GetCompartmentWeight(i); + outputModel->GetCompartment(i)->SetCompartmentVector(inputModel->GetCompartment(i)->GetCompartmentVector()); + } + + unsigned int pos = numIsoCompartments; + for (unsigned int i = numIsoCompartments;i < outputModel->GetNumberOfCompartments();++i) + { + double compWeight = inputModel->GetCompartmentWeight(i); + if (compWeight <= 0) + continue; + + weights[pos] = compWeight; + outputModel->GetCompartment(pos)->SetCompartmentVector(inputModel->GetCompartment(i)->GetCompartmentVector()); + ++pos; + } + + outputModel->SetCompartmentWeights(weights); + outputVector = outputModel->GetModelVector(); + + outItr.Set(outputVector); + + ++outItr; + ++inItr; + this->IncrementNumberOfProcessedPoints(); + } +} + +} // end namespace anima diff --git a/Anima/diffusion/mcm_model_averaging/animaMCMModelAveraging.cxx b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveraging.cxx new file mode 100644 index 000000000..bd54af3f5 --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveraging.cxx @@ -0,0 +1,206 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +//Update progression of the process +void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) +{ + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< inArg("i","input-mcm","MCM images list as a text file",true,"","MCM images list",cmd); + TCLAP::ValueArg inB0Arg("b","input-b0","B0 images list as a text file",false,"","B0 images list",cmd); + TCLAP::ValueArg inNoiseArg("n","input-noise","Noise images list as a text file",false,"","Noise images list",cmd); + TCLAP::ValueArg aiccArg("a","aicc","AICc images list as a text file",true,"","AICc images list",cmd); + TCLAP::ValueArg resArg("o","output","Result MCM volume",true,"","result MCM image",cmd); + TCLAP::ValueArg resB0Arg("O","output-b0","Result B0 volume",false,"","result B0 image",cmd); + TCLAP::ValueArg resNoiseArg("N","output-noise","Result noise volume",false,"","result noise image",cmd); + + TCLAP::ValueArg modelSelectionArg("m","model-selection","output model selection map",false,""," output selection map",cmd); + TCLAP::SwitchArg sqSimArg("S", "squared-similarity", "Use squared similarity",cmd,false); + TCLAP::SwitchArg clusterArg("C", "model-simplify", "Use clustering to simplify models",cmd,false); + + TCLAP::ValueArg weightThrArg("t","weight-thr","Minimal weight for considering a fascicle compartment (default: 0.05)",false,0.05,"weight threshold",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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + std::ifstream mcmsFile(inArg.getValue().c_str()); + if (!mcmsFile.is_open()) + { + std::cerr << "Please provide usable file with input MCMs" << std::endl; + return EXIT_FAILURE; + } + + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + + typedef anima::MCMModelAveragingImageFilter FilterType; + FilterType::Pointer mainFilter = FilterType::New(); + + unsigned int numInput = 0; + while (!mcmsFile.eof()) + { + char tmpStr[2048]; + mcmsFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading image " << numInput << ": " << tmpStr << std::endl; + + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(tmpStr); + mcmReader.Update(); + + mainFilter->SetInput(numInput,mcmReader.GetModelVectorImage()); + numInput++; + } + + // Load AICc files + std::ifstream aiccFile(aiccArg.getValue().c_str()); + + if (!aiccFile.is_open()) + { + std::cerr << "Please provide usable file with input AICcs" << std::endl; + return EXIT_FAILURE; + } + + numInput = 0; + while (!aiccFile.eof()) + { + char tmpStr[2048]; + aiccFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading AICc image " << numInput << ": " << tmpStr << std::endl; + mainFilter->SetAICcVolume(numInput,anima::readImage(tmpStr)); + + numInput++; + } + + aiccFile.close(); + + if ((inB0Arg.getValue() != "")&&(resB0Arg.getValue() != "")) + { + // Load B0 files + std::ifstream b0File(inB0Arg.getValue().c_str()); + + if (!b0File.is_open()) + { + std::cerr << "Please provide usable file with input B0s" << std::endl; + return EXIT_FAILURE; + } + + numInput = 0; + while (!b0File.eof()) + { + char tmpStr[2048]; + b0File.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading B0 image " << numInput << ": " << tmpStr << std::endl; + mainFilter->SetB0Volume(numInput,anima::readImage(tmpStr)); + + numInput++; + } + + b0File.close(); + } + + if ((inNoiseArg.getValue() != "")&&(resNoiseArg.getValue() != "")) + { + // Load noise files + std::ifstream noiseFile(inNoiseArg.getValue().c_str()); + + if (!noiseFile.is_open()) + { + std::cerr << "Please provide usable file with input noise images" << std::endl; + return EXIT_FAILURE; + } + + numInput = 0; + while (!noiseFile.eof()) + { + char tmpStr[2048]; + noiseFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading noise image " << numInput << ": " << tmpStr << std::endl; + mainFilter->SetNoiseVolume(numInput,anima::readImage(tmpStr)); + + numInput++; + } + + noiseFile.close(); + } + + mainFilter->AddObserver(itk::ProgressEvent(), callback); + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + mainFilter->SetWeightThreshold(weightThrArg.getValue()); + mainFilter->SetSquaredSimilarity(sqSimArg.isSet()); + mainFilter->SetSimplifyModels(clusterArg.isSet()); + + itk::TimeProbe tmpTimer; + + tmpTimer.Start(); + + try + { + mainFilter->Update(); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTimer.Stop(); + + std::cout << "\nAveraging done in " << tmpTimer.GetTotal() << " s" << std::endl; + + anima::MCMFileWriter writer; + + writer.SetInputImage(mainFilter->GetOutput()); + writer.SetFileName(resArg.getValue()); + + writer.Update(); + + if (modelSelectionArg.getValue() != "") + anima::writeImage (modelSelectionArg.getValue(),mainFilter->GetMoseMap()); + + if ((inB0Arg.getValue() != "")&&(resB0Arg.getValue() != "")) + anima::writeImage (resB0Arg.getValue(),mainFilter->GetOutputB0Volume()); + + if ((inNoiseArg.getValue() != "")&&(resNoiseArg.getValue() != "")) + anima::writeImage (resNoiseArg.getValue(),mainFilter->GetOutputNoiseVolume()); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.h b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.h new file mode 100644 index 000000000..2f29cbdb6 --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace anima +{ + +template +class MCMModelAveragingImageFilter : +public anima::NumberedThreadImageToImageFilter < anima::MCMImage, anima::MCMImage > +{ +public: + /** Standard class typedefs. */ + typedef MCMModelAveragingImageFilter Self; + typedef anima::MCMImage InputImageType; + typedef anima::MCMImage OutputImageType; + typedef itk::Image ScalarImageType; + typedef itk::Image MoseImageType; + typedef itk::Image Image4DType; + typedef anima::NumberedThreadImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + typedef anima::MultiCompartmentModel MCModelType; + typedef typename MCModelType::Pointer MCModelPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Run-time type information (and related methods) */ + itkTypeMacro(MCMModelAveragingImageFilter, anima::NumberedThreadImageToImageFilter) + + /** Image typedef support */ + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + typedef typename ScalarImageType::Pointer ScalarImagePointer; + typedef typename OutputImageType::PixelType OutputPixelType; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + /** Tensor typdefs. */ + typedef vnl_matrix_fixed MatrixType; + typedef vnl_vector_fixed VectorType; + typedef itk::SymmetricEigenAnalysis EigenAnalysisType; + + typedef anima::BaseCompartment BaseCompartmentType; + typedef std::vector ModelDataType; + typedef std::vector WeightDataType; + + itkSetMacro(WeightThreshold, double) + void SetAICcVolume(unsigned int i, ScalarImageType *vol); + void SetB0Volume(unsigned int i, ScalarImageType *vol); + void SetNoiseVolume(unsigned int i, ScalarImageType *vol); + + itkSetMacro(SquaredSimilarity,bool) + itkSetMacro(SimplifyModels,bool) + + MoseImageType *GetMoseMap () {return m_MoseMap;} + ScalarImageType *GetOutputB0Volume () {return m_OutputB0Volume;} + ScalarImageType *GetOutputNoiseVolume () {return m_OutputNoiseVolume;} + +protected: + MCMModelAveragingImageFilter() + : Superclass() + { + m_WeightThreshold = 0.05; + m_SquaredSimilarity = false; + m_SimplifyModels = false; + } + + virtual ~MCMModelAveragingImageFilter() {} + + void GenerateOutputInformation() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + void AfterThreadedGenerateData() ITK_OVERRIDE; + + void InitializeReferenceOutputModel(); + void IncrementModelPairingVector(std::vector &modelPairingVector); + WeightDataType GetAkaikeWeights(const WeightDataType &aicData); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(MCMModelAveragingImageFilter); + + std::vector m_ReferenceModels; + MCModelPointer m_ReferenceOutputModel; + std::vector m_AICcVolumes; + std::vector m_B0Volumes; + std::vector m_NoiseVolumes; + std::vector m_WorkNonFreeWaterCorrespondences, m_NumberOfNonFreeWaterCompartments; + + unsigned int m_NumberOfIsotropicCompartments; + + ScalarImagePointer m_OutputB0Volume; + ScalarImagePointer m_OutputNoiseVolume; + MoseImageType::Pointer m_MoseMap; + + double m_WeightThreshold; + bool m_SquaredSimilarity; + bool m_SimplifyModels; + + static const double m_ZeroThreshold; +}; + +} // end of namespace anima + +#include "animaMCMModelAveragingImageFilter.hxx" diff --git a/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.hxx b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.hxx new file mode 100644 index 000000000..3ac24bf0b --- /dev/null +++ b/Anima/diffusion/mcm_model_averaging/animaMCMModelAveragingImageFilter.hxx @@ -0,0 +1,793 @@ +#pragma once +#include "animaMCMModelAveragingImageFilter.h" +#include + +#include +#include + +#include +#include + +#include +#include + +namespace anima +{ + +template const double MCMModelAveragingImageFilter::m_ZeroThreshold = 1e-6; + +template +void +MCMModelAveragingImageFilter +::SetAICcVolume(unsigned int i, ScalarImageType *vol) +{ + if (i == m_AICcVolumes.size()) + m_AICcVolumes.push_back(vol); + else if (i > m_AICcVolumes.size()) + { + itkExceptionMacro("Trying to add a non contiguous AICc volume... Add AICc volumes contiguously (0,1,2,3,...)..."); + } + else + m_AICcVolumes[i] = vol; +} + +template +void +MCMModelAveragingImageFilter +::SetB0Volume(unsigned int i, ScalarImageType *vol) +{ + if (i == m_B0Volumes.size()) + m_B0Volumes.push_back(vol); + else if (i > m_B0Volumes.size()) + { + itkExceptionMacro("Trying to add a non contiguous B0 volume... Add B0 volumes contiguously (0,1,2,3,...)..."); + } + else + m_B0Volumes[i] = vol; +} + +template +void +MCMModelAveragingImageFilter +::SetNoiseVolume(unsigned int i, ScalarImageType *vol) +{ + if (i == m_NoiseVolumes.size()) + m_NoiseVolumes.push_back(vol); + else if (i > m_NoiseVolumes.size()) + { + itkExceptionMacro("Trying to add a non contiguous noise volume... Add noise volumes contiguously (0,1,2,3,...)..."); + } + else + m_NoiseVolumes[i] = vol; +} + +template +void +MCMModelAveragingImageFilter +::GenerateOutputInformation() +{ + // Override the method in itkImageSource, so we can set the vector length of + // the output itk::VectorImage + + this->Superclass::GenerateOutputInformation(); + this->InitializeReferenceOutputModel(); + + OutputImageType *output = this->GetOutput(); + output->SetVectorLength(m_ReferenceOutputModel->GetSize()); +} + +template +void +MCMModelAveragingImageFilter +::InitializeReferenceOutputModel() +{ + if (m_ReferenceModels.size() == 0) + { + m_ReferenceModels.resize(this->GetNumberOfIndexedInputs()); + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + { + InputImageType *input = const_cast (this->GetInput(i)); + m_ReferenceModels[i] = input->GetDescriptionModel(); + } + } + + unsigned int numberOfCombinations = 1; + + m_WorkNonFreeWaterCorrespondences.clear(); + m_NumberOfNonFreeWaterCompartments.clear(); + m_NumberOfIsotropicCompartments = 0; + + // We assume that all non free water compartments are of the same type + anima::DiffusionModelCompartmentType compartmentType; + bool modelWithIRW = false; + bool modelWithSW = false; + bool modelWithFW = false; + bool modelWithStanisz = false; + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + { + unsigned int numberOfCompartments = m_ReferenceModels[i]->GetNumberOfCompartments(); + + if (i == 0) + { + m_NumberOfIsotropicCompartments = m_ReferenceModels[i]->GetNumberOfIsotropicCompartments(); + for (unsigned int j = 0;j < m_NumberOfIsotropicCompartments;++j) + { + anima::DiffusionModelCompartmentType isoCompType = m_ReferenceModels[i]->GetCompartment(j)->GetCompartmentType(); + + switch (isoCompType) + { + case anima::FreeWater: + modelWithFW = true; + break; + case anima::StationaryWater: + modelWithSW = true; + break; + case anima::Stanisz: + modelWithStanisz = true; + break; + case anima::IsotropicRestrictedWater: + default: + modelWithIRW = true; + break; + } + } + } + + if (m_ReferenceModels[i]->GetNumberOfIsotropicCompartments() != m_NumberOfIsotropicCompartments) + itkExceptionMacro("All input models should have the same number of isotropic compartments."); + + unsigned int numberOfNonFreeWaterCompartments = numberOfCompartments - m_NumberOfIsotropicCompartments; + + if (numberOfNonFreeWaterCompartments > 0) + { + compartmentType = m_ReferenceModels[i]->GetCompartment(m_NumberOfIsotropicCompartments)->GetCompartmentType(); + + numberOfCombinations *= numberOfNonFreeWaterCompartments; + m_WorkNonFreeWaterCorrespondences.push_back(i); + m_NumberOfNonFreeWaterCompartments.push_back(numberOfNonFreeWaterCompartments); + } + } + + typedef anima::MultiCompartmentModelCreator MCMCreatorType; + MCMCreatorType mcmCreator; + + mcmCreator.SetModelWithFreeWaterComponent(modelWithFW); + mcmCreator.SetModelWithStationaryWaterComponent(modelWithSW); + mcmCreator.SetModelWithRestrictedWaterComponent(modelWithIRW); + mcmCreator.SetModelWithStaniszComponent(modelWithStanisz); + + mcmCreator.SetCompartmentType(compartmentType); + mcmCreator.SetNumberOfCompartments(3 * numberOfCombinations); + + m_ReferenceOutputModel = mcmCreator.GetNewMultiCompartmentModel(); + this->GetOutput()->SetDescriptionModel(m_ReferenceOutputModel); +} + +template +void +MCMModelAveragingImageFilter +::BeforeThreadedGenerateData() +{ + Superclass::BeforeThreadedGenerateData(); + + if (m_AICcVolumes.size() != this->GetNumberOfIndexedInputs()) + { + std::string error("There should be the same number of input images and input AICc volumes... "); + error += m_AICcVolumes.size(); + error += " "; + error += this->GetNumberOfIndexedInputs(); + throw itk::ExceptionObject(__FILE__, __LINE__,error,ITK_LOCATION); + } + + if (!m_ReferenceOutputModel) + this->InitializeReferenceOutputModel(); + + // Create Mose map + m_MoseMap = MoseImageType::New(); + m_MoseMap->Initialize(); + m_MoseMap->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + m_MoseMap->SetSpacing (this->GetInput(0)->GetSpacing()); + m_MoseMap->SetOrigin (this->GetInput(0)->GetOrigin()); + m_MoseMap->SetDirection (this->GetInput(0)->GetDirection()); + m_MoseMap->Allocate(); + m_MoseMap->FillBuffer(0); + + if (m_B0Volumes.size() != 0) + { + m_OutputB0Volume = ScalarImageType::New(); + m_OutputB0Volume->Initialize(); + m_OutputB0Volume->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + m_OutputB0Volume->SetSpacing (this->GetInput(0)->GetSpacing()); + m_OutputB0Volume->SetOrigin (this->GetInput(0)->GetOrigin()); + m_OutputB0Volume->SetDirection (this->GetInput(0)->GetDirection()); + m_OutputB0Volume->Allocate(); + m_OutputB0Volume->FillBuffer(0); + } + + if (m_NoiseVolumes.size() != 0) + { + m_OutputNoiseVolume = ScalarImageType::New(); + m_OutputNoiseVolume->Initialize(); + m_OutputNoiseVolume->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + m_OutputNoiseVolume->SetSpacing (this->GetInput(0)->GetSpacing()); + m_OutputNoiseVolume->SetOrigin (this->GetInput(0)->GetOrigin()); + m_OutputNoiseVolume->SetDirection (this->GetInput(0)->GetDirection()); + m_OutputNoiseVolume->Allocate(); + m_OutputNoiseVolume->FillBuffer(0); + } +} + +template +void +MCMModelAveragingImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + // Iterator definitions + typedef itk::ImageRegionConstIterator ImageIteratorType; + + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + std::vector inIterators(numInputs); + for (unsigned int i = 0;i < numInputs;++i) + inIterators[i] = ImageIteratorType(this->GetInput(i),outputRegionForThread); + + typedef itk::ImageRegionIterator OutImageIteratorType; + OutImageIteratorType outIterator(this->GetOutput(),outputRegionForThread); + + typedef itk::ImageRegionIterator MoseImageIteratorType; + MoseImageIteratorType moseIterator(m_MoseMap,outputRegionForThread); + + typedef itk::ImageRegionIterator ScalarImageIteratorType; + std::vector aiccIterators(numInputs); + for (unsigned int i = 0;i < numInputs;++i) + aiccIterators[i] = ScalarImageIteratorType(m_AICcVolumes[i],outputRegionForThread); + + std::vector b0Iterators(numInputs); + ScalarImageIteratorType outputB0Iterator; + if (m_OutputB0Volume) + { + for (unsigned int i = 0;i < numInputs;++i) + b0Iterators[i] = ScalarImageIteratorType(m_B0Volumes[i],outputRegionForThread); + + outputB0Iterator = ScalarImageIteratorType(m_OutputB0Volume,outputRegionForThread); + } + + std::vector noiseIterators(numInputs); + ScalarImageIteratorType outputNoiseIterator; + if (m_OutputNoiseVolume) + { + for (unsigned int i = 0;i < numInputs;++i) + noiseIterators[i] = ScalarImageIteratorType(m_NoiseVolumes[i],outputRegionForThread); + + outputNoiseIterator = ScalarImageIteratorType(m_OutputNoiseVolume,outputRegionForThread); + } + + MCModelPointer referenceOutputModel = m_ReferenceOutputModel->Clone(); + MCModelPointer outputModel = m_ReferenceOutputModel->Clone(); + + std::vector referenceModels(m_ReferenceModels.size()); + for (unsigned int i = 0;i < referenceModels.size();++i) + referenceModels[i] = m_ReferenceModels[i]->Clone(); + + // Initialize output vector + unsigned int outVectorSize = referenceOutputModel->GetSize(); + OutputPixelType vecImage(outVectorSize); + OutputPixelType resVecImage(outVectorSize); + BaseCompartmentType::ModelOutputVectorType resVecModel(outVectorSize); + + // Data for eigen analysis + MatrixType eigVecs; + VectorType eigVals; + + // Actual work data + WeightDataType modelAICs(numInputs,0); + WeightDataType modelAICWeights(numInputs,0); + + unsigned int pairingVectorSize = m_WorkNonFreeWaterCorrespondences.size(); + unsigned int lastModelDimension = m_NumberOfNonFreeWaterCompartments[pairingVectorSize - 1]; + std::vector modelPairingVector(m_WorkNonFreeWaterCorrespondences.size(),0); + BaseCompartmentType::ListType outputCompartmentWeights(referenceOutputModel->GetNumberOfCompartments(),0); + vnl_matrix connectivityMatrix, distanceMatrix; + MatrixType dcmData; + VectorType fascicleDirection, fascicleDirection2; + std::vector clusterMembers; + + while (!outIterator.IsAtEnd()) + { + resVecImage.Fill(0.0); + resVecModel.Fill(0.0); + + bool uselessVoxel = true; + for (unsigned int i = 0;i < numInputs;++i) + { + if (aiccIterators[i].Get() != 0) + { + uselessVoxel = false; + break; + } + } + + if (uselessVoxel) + { + outIterator.Set(resVecImage); + ++outIterator; + + moseIterator.Set(0); + ++moseIterator; + + for (unsigned int i = 0;i < numInputs;++i) + { + ++inIterators[i]; + ++aiccIterators[i]; + } + + if (m_OutputB0Volume) + { + for (unsigned int i = 0;i < numInputs;++i) + ++b0Iterators[i]; + + ++outputB0Iterator; + } + + if (m_OutputNoiseVolume) + { + for (unsigned int i = 0;i < numInputs;++i) + ++noiseIterators[i]; + + ++outputNoiseIterator; + } + + this->IncrementNumberOfProcessedPoints(); + continue; + } + + std::fill(outputCompartmentWeights.begin(),outputCompartmentWeights.end(),0); + outputModel->SetModelVector(resVecModel); + referenceOutputModel->SetModelVector(resVecModel); + + for (unsigned int i = 0;i < numInputs;++i) + { + vecImage = inIterators[i].Get(); + referenceModels[i]->SetModelVector(vecImage); + modelAICs[i] = aiccIterators[i].Get(); + } + + modelAICWeights = this->GetAkaikeWeights(modelAICs); + + // Start by easy and optional things + if (m_OutputB0Volume) + { + double outputB0Value = 0; + for (unsigned int i = 0;i < numInputs;++i) + outputB0Value += modelAICWeights[i] * b0Iterators[i].Get(); + + outputB0Iterator.Set(outputB0Value); + } + + if (m_OutputNoiseVolume) + { + double outputNoiseValue = 0; + for (unsigned int i = 0;i < numInputs;++i) + outputNoiseValue += modelAICWeights[i] * noiseIterators[i].Get(); + + outputNoiseIterator.Set(outputNoiseValue); + } + + double sumIsoWeights = 0; + // Perform free water model averaging, filling directly output model here + if (m_NumberOfIsotropicCompartments > 0) + { + for (unsigned int i = 0;i < m_NumberOfIsotropicCompartments;++i) + { + double axialDiffusivity = 0; + double tissueRadius = 0; + double averageIsoWeight = 0; + + double sumWeights = 0; + for (unsigned int j = 0;j < numInputs;++j) + { + double weight = modelAICWeights[j]; + double compWeight = referenceModels[j]->GetCompartmentWeight(i); + if (compWeight == 0) + continue; + + axialDiffusivity += weight * referenceModels[j]->GetCompartment(i)->GetAxialDiffusivity(); + tissueRadius += weight * referenceModels[j]->GetCompartment(i)->GetTissueRadius(); + averageIsoWeight += weight * compWeight; + sumWeights += weight; + } + + if (sumWeights > 0) + { + outputModel->GetCompartment(i)->SetAxialDiffusivity(axialDiffusivity / sumWeights); + outputModel->GetCompartment(i)->SetTissueRadius(tissueRadius / sumWeights); + } + + outputCompartmentWeights[i] = averageIsoWeight; + sumIsoWeights += averageIsoWeight; + } + } + + unsigned int modelOutputPosition = m_NumberOfIsotropicCompartments; + + if (sumIsoWeights > 1.0 - m_WeightThreshold) + { + for (unsigned int i = 0;i < m_NumberOfIsotropicCompartments;++i) + outputCompartmentWeights[i] /= sumIsoWeights; + + sumIsoWeights = 1.0; + } + else + { + // Now directional models averaging + std::fill(modelPairingVector.begin(),modelPairingVector.end(),0); + unsigned int numberOfPossibleCombinations = std::tgamma(lastModelDimension+1); + + while (modelPairingVector[pairingVectorSize - 1] < lastModelDimension) + { + // Compute combination + dcmData.fill(0); + double sumWeights = 0; + double averagePerpendicularAngle = 0; + double averageAxialDiffusivity = 0; + double averageTissueRadius = 0; + double averageRadialDiffusivity1 = 0; + double averageRadialDiffusivity2 = 0; + double averageOrientationConcentration = 0; + double averageExtraAxonalFraction = 0; + double averageCompartmentWeight = 0; + + for (unsigned int i = 0;i < modelPairingVector.size();++i) + { + unsigned int modelIndex = m_WorkNonFreeWaterCorrespondences[i]; + unsigned int compartmentIndex = m_NumberOfIsotropicCompartments + modelPairingVector[i]; + + double weight = modelAICWeights[modelIndex]; + double compWeight = referenceModels[modelIndex]->GetCompartmentWeight(compartmentIndex) * modelIndex / numberOfPossibleCombinations; + if ((weight == 0.0)||(compWeight == 0.0)) + continue; + + sumWeights += weight; + BaseCompartmentType *modelCompartment = referenceModels[modelIndex]->GetCompartment(compartmentIndex); + anima::TransformSphericalToCartesianCoordinates(modelCompartment->GetOrientationTheta(),modelCompartment->GetOrientationPhi(),1.0,fascicleDirection); + + for (unsigned int j = 0;j < 3;++j) + for (unsigned int k = j;k < 3;++k) + { + double val = weight * fascicleDirection[j] * fascicleDirection[k]; + dcmData(j,k) += val; + } + + averagePerpendicularAngle += weight * modelCompartment->GetPerpendicularAngle(); + averageAxialDiffusivity += weight * modelCompartment->GetAxialDiffusivity(); + averageTissueRadius += weight * modelCompartment->GetTissueRadius(); + averageRadialDiffusivity1 += weight * modelCompartment->GetRadialDiffusivity1(); + averageRadialDiffusivity2 += weight * modelCompartment->GetRadialDiffusivity2(); + averageOrientationConcentration += weight * modelCompartment->GetOrientationConcentration(); + averageExtraAxonalFraction += weight * modelCompartment->GetExtraAxonalFraction(); + averageCompartmentWeight += weight * compWeight; + } + + if (sumWeights < m_ZeroThreshold) + { + // Increment pairing vector and get to next combination + this->IncrementModelPairingVector(modelPairingVector); + continue; + } + + for (unsigned int j = 0;j < 3;++j) + for (unsigned int k = j + 1;k < 3;++k) + dcmData(k,j) = dcmData(j,k); + + dcmData /= sumWeights; + averagePerpendicularAngle /= sumWeights; + averageAxialDiffusivity /= sumWeights; + averageTissueRadius /= sumWeights; + averageRadialDiffusivity1 /= sumWeights; + averageRadialDiffusivity2 /= sumWeights; + averageOrientationConcentration /= sumWeights; + averageExtraAxonalFraction /= sumWeights; + + EigenAnalysisType eigSystem(3); + eigSystem.SetOrderEigenValues(true); + eigSystem.ComputeEigenValuesAndVectors(dcmData, eigVals, eigVecs); + + unsigned int minPosition = modelOutputPosition; + sumWeights = 0; + + for (unsigned int j = 0;j < 3;++j) + { + double eigVal = eigVals[j]; + + if (3.0 * eigVal < 1.0) + continue; + + sumWeights += eigVal; + + anima::TransformCartesianToSphericalCoordinates(eigVecs.get_row(j),fascicleDirection); + + BaseCompartmentType *outputModelCompartment = referenceOutputModel->GetCompartment(modelOutputPosition); + + outputModelCompartment->SetOrientationTheta(fascicleDirection[0]); + outputModelCompartment->SetOrientationPhi(fascicleDirection[1]); + outputModelCompartment->SetPerpendicularAngle(averagePerpendicularAngle); + outputModelCompartment->SetTissueRadius(averageTissueRadius); + outputModelCompartment->SetAxialDiffusivity(averageAxialDiffusivity); + outputModelCompartment->SetRadialDiffusivity1(averageRadialDiffusivity1); + outputModelCompartment->SetRadialDiffusivity2(averageRadialDiffusivity2); + outputModelCompartment->SetOrientationConcentration(averageOrientationConcentration); + outputModelCompartment->SetExtraAxonalFraction(averageExtraAxonalFraction); + outputCompartmentWeights[modelOutputPosition] = averageCompartmentWeight * eigVal; + + ++modelOutputPosition; + } + + unsigned int maxPosition = modelOutputPosition; + + while (minPosition < maxPosition) + { + outputCompartmentWeights[minPosition] /= sumWeights; + ++minPosition; + } + + // Increment pairing vector + this->IncrementModelPairingVector(modelPairingVector); + } + } + + double checkSum = 0; + for (unsigned int i = 0;i < outputCompartmentWeights.size();++i) + checkSum += outputCompartmentWeights[i]; + + if (std::abs(checkSum - 1.0) > m_ZeroThreshold) + itkExceptionMacro("ERROR: Compartment weights do not sum to one"); + + referenceOutputModel->SetCompartmentWeights(outputCompartmentWeights); + + unsigned int realNumberOfCombinations = modelOutputPosition - m_NumberOfIsotropicCompartments; + + if (m_SimplifyModels && realNumberOfCombinations > 1) + { + // Now perform output model simplification + // modularity clustering wants adjacency matrix + + connectivityMatrix.set_size(realNumberOfCombinations,realNumberOfCombinations); + connectivityMatrix.fill(0.0); + + double anisotropicWeightSum = 0; + for (unsigned int i = m_NumberOfIsotropicCompartments;i < modelOutputPosition;++i) + anisotropicWeightSum += outputCompartmentWeights[i]; + + for (unsigned int i = 0;i < realNumberOfCombinations;++i) + connectivityMatrix(i,i) = outputCompartmentWeights[i+m_NumberOfIsotropicCompartments] / anisotropicWeightSum; + + for (unsigned int i = 0;i < realNumberOfCombinations;++i) + { + BaseCompartmentType *modelCompartment1 = referenceOutputModel->GetCompartment(i+m_NumberOfIsotropicCompartments); + anima::TransformSphericalToCartesianCoordinates(modelCompartment1->GetOrientationTheta(),modelCompartment1->GetOrientationPhi(),1.0,fascicleDirection); + + for (unsigned int j = i+1;j < realNumberOfCombinations;++j) + { + BaseCompartmentType *modelCompartment2 = referenceOutputModel->GetCompartment(j+m_NumberOfIsotropicCompartments); + anima::TransformSphericalToCartesianCoordinates(modelCompartment2->GetOrientationTheta(),modelCompartment2->GetOrientationPhi(),1.0,fascicleDirection2); + + double simValue = anima::ComputeScalarProduct(fascicleDirection,fascicleDirection2); + + if (m_SquaredSimilarity) + simValue *= simValue; + else + simValue = std::abs(simValue); + + connectivityMatrix(i,j) = simValue; + connectivityMatrix(j,i) = simValue; + } + } + + anima::ModularityClusteringFilter modFilter; + modFilter.SetInputData(connectivityMatrix); + modFilter.Update(); + + unsigned int numberOfClusters = modFilter.GetNumberOfClusters(); + + modelOutputPosition = m_NumberOfIsotropicCompartments; + std::fill(outputCompartmentWeights.begin()+modelOutputPosition,outputCompartmentWeights.end(),0); + + for (unsigned int i = 0;i < numberOfClusters;++i) + { + clusterMembers = modFilter.GetReverseClassMembership(i); + unsigned int clusterSize = clusterMembers.size(); + + BaseCompartmentType *outputModelCompartment = outputModel->GetCompartment(modelOutputPosition); + + dcmData.fill(0.0); + double sumWeights = 0; + double averagePerpendicularAngle = 0; + double averageTissueRadius = 0; + double averageAxialDiffusivity = 0; + double averageRadialDiffusivity1 = 0; + double averageRadialDiffusivity2 = 0; + double averageOrientationConcentration = 0; + double averageExtraAxonalFraction = 0; + + for (unsigned int j = 0;j < clusterSize;++j) + { + unsigned int compartmentIndex = m_NumberOfIsotropicCompartments + clusterMembers[j]; + + BaseCompartmentType *modelCompartment = referenceOutputModel->GetCompartment(compartmentIndex); + double compartmentWeight = referenceOutputModel->GetCompartmentWeight(compartmentIndex); + anima::TransformSphericalToCartesianCoordinates(modelCompartment->GetOrientationTheta(),modelCompartment->GetOrientationPhi(),1.0,fascicleDirection); + + // orientation averaging + for (unsigned int k = 0;k < 3;++k) + { + for (unsigned int l = k;l < 3;++l) + { + double t = fascicleDirection[k] * fascicleDirection[l]; + + dcmData(k,l) += t; + + if (k != l) + dcmData(l,k) += t; + } + } + + averagePerpendicularAngle += modelCompartment->GetPerpendicularAngle(); + averageTissueRadius += modelCompartment->GetTissueRadius(); + averageAxialDiffusivity += modelCompartment->GetAxialDiffusivity(); + averageRadialDiffusivity1 += modelCompartment->GetRadialDiffusivity1(); + averageRadialDiffusivity2 += modelCompartment->GetRadialDiffusivity2(); + averageOrientationConcentration += modelCompartment->GetOrientationConcentration(); + averageExtraAxonalFraction += modelCompartment->GetExtraAxonalFraction(); + sumWeights += compartmentWeight; + } + + EigenAnalysisType eigSystem(3); + eigSystem.SetOrderEigenValues(true); + eigSystem.ComputeEigenValuesAndVectors(dcmData, eigVals, eigVecs); + + anima::TransformCartesianToSphericalCoordinates(eigVecs.get_row(2),fascicleDirection); + outputModelCompartment->SetOrientationTheta(fascicleDirection[0]); + outputModelCompartment->SetOrientationPhi(fascicleDirection[1]); + + averagePerpendicularAngle /= clusterSize; + averageTissueRadius /= clusterSize; + averageAxialDiffusivity /= clusterSize; + averageRadialDiffusivity1 /= clusterSize; + averageRadialDiffusivity2 /= clusterSize; + averageOrientationConcentration /= clusterSize; + averageExtraAxonalFraction /= clusterSize; + + outputModelCompartment->SetPerpendicularAngle(averagePerpendicularAngle); + outputModelCompartment->SetTissueRadius(averageTissueRadius); + outputModelCompartment->SetAxialDiffusivity(averageAxialDiffusivity); + outputModelCompartment->SetRadialDiffusivity1(averageRadialDiffusivity1); + outputModelCompartment->SetRadialDiffusivity2(averageRadialDiffusivity2); + outputModelCompartment->SetOrientationConcentration(averageOrientationConcentration); + outputModelCompartment->SetExtraAxonalFraction(averageExtraAxonalFraction); + + outputCompartmentWeights[modelOutputPosition] = sumWeights; + + ++modelOutputPosition; + } + } // end model simplification + + checkSum = 0; + for (unsigned int i = 0;i < outputCompartmentWeights.size();++i) + checkSum += outputCompartmentWeights[i]; + + if (std::abs(checkSum - 1.0) > m_ZeroThreshold) + itkExceptionMacro("ERROR: Compartment weights do not sum to one after simplification"); + + outputModel->SetCompartmentWeights(outputCompartmentWeights); + + resVecModel = outputModel->GetModelVector(); + for (unsigned int i = 0;i < resVecModel.GetSize();++i) + resVecImage[i] = resVecModel[i]; + + outIterator.Set(resVecImage); + + moseIterator.Set(modelOutputPosition - m_NumberOfIsotropicCompartments); + + for (unsigned int i = 0;i < numInputs;++i) + { + ++inIterators[i]; + ++aiccIterators[i]; + } + + if (m_OutputB0Volume) + { + for (unsigned int i = 0;i < numInputs;++i) + ++b0Iterators[i]; + + ++outputB0Iterator; + } + + if (m_OutputNoiseVolume) + { + for (unsigned int i = 0;i < numInputs;++i) + ++noiseIterators[i]; + + ++outputNoiseIterator; + } + + ++outIterator; + ++moseIterator; + this->IncrementNumberOfProcessedPoints(); + } +} + +template +void +MCMModelAveragingImageFilter +::AfterThreadedGenerateData() +{ + unsigned int numOutputCompartments = m_ReferenceOutputModel->GetNumberOfCompartments() - m_ReferenceOutputModel->GetNumberOfIsotropicCompartments(); + if ((!m_SimplifyModels)||(numOutputCompartments <= 1)) + return; + + std::cout << "\nAveraging performed, now simplifying models..." << std::endl; + typedef anima::MCMImageSimplifier ImageSimplifierType; + typename ImageSimplifierType::Pointer simplifier = ImageSimplifierType::New(); + + simplifier->SetMoseVolume(m_MoseMap); + simplifier->SetInput(this->GetOutput()); + simplifier->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + simplifier->Update(); + + OutputImagePointer simplifiedImage = simplifier->GetOutput(); + simplifiedImage->DisconnectPipeline(); + + this->SetNthOutput(0,simplifiedImage); +} + +template +void +MCMModelAveragingImageFilter +::IncrementModelPairingVector(std::vector &modelPairingVector) +{ + unsigned int pos = 0; + + modelPairingVector[pos]++; + + while ((modelPairingVector[pos] >= m_NumberOfNonFreeWaterCompartments[pos])&&(pos < modelPairingVector.size() - 1)) + { + modelPairingVector[pos] = 0; + ++pos; + modelPairingVector[pos]++; + } +} + +template +typename MCMModelAveragingImageFilter::WeightDataType +MCMModelAveragingImageFilter +::GetAkaikeWeights(const WeightDataType &aicData) +{ + unsigned int nbInputs = aicData.size(); + + WeightDataType modelWeights(nbInputs, 0); + + double minAIC = aicData[0]; + for (unsigned int i = 1;i < nbInputs;++i) + { + double tmpAIC = aicData[i]; + + if (tmpAIC < minAIC) + minAIC = tmpAIC; + } + + double sumModelWeights = 0; + for (unsigned int i = 0;i < nbInputs;++i) + { + double tmpVal = exp((minAIC - aicData[i]) / 2.0); + modelWeights[i] = tmpVal; + sumModelWeights += tmpVal; + } + + for (unsigned int i = 0;i < nbInputs;++i) + modelWeights[i] /= sumModelWeights; + + return modelWeights; +} + +} // end namespace anima diff --git a/Anima/diffusion/mcm_tools/CMakeLists.txt b/Anima/diffusion/mcm_tools/CMakeLists.txt index 90690828e..911579ba5 100644 --- a/Anima/diffusion/mcm_tools/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/CMakeLists.txt @@ -1,2 +1,8 @@ +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_block_images) add_subdirectory(mcm_scalar_maps) +add_subdirectory(mt_estimation_validation) +add_subdirectory(test_averaging) \ No newline at end of file diff --git a/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/CMakeLists.txt b/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/CMakeLists.txt new file mode 100644 index 000000000..a848588ce --- /dev/null +++ b/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/CMakeLists.txt @@ -0,0 +1,39 @@ +if(BUILD_TOOLS) + +project(animaDWISimulationFromMCM) + +## ############################################################################# +## 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 + AnimaOptimizers + AnimaSpecialFunctions + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/animaDWISimulationFromMCM.cxx b/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/animaDWISimulationFromMCM.cxx new file mode 100644 index 000000000..dc87dc523 --- /dev/null +++ b/Anima/diffusion/mcm_tools/dwi_simulation_from_mcm/animaDWISimulationFromMCM.cxx @@ -0,0 +1,147 @@ +#include + +#include +#include + +#include +#include +#include + +#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","inputmcm","mcm image",true,"","mcm image",cmd); + TCLAP::ValueArg resArg("o","output","Result DWI volume",true,"","result DWI volume",cmd); + TCLAP::ValueArg smallDeltaArg("", "small-delta", "Diffusion small delta (in seconds)", false, anima::DiffusionSmallDelta, "small delta", cmd); + TCLAP::ValueArg bigDeltaArg("", "big-delta", "Diffusion big delta (in seconds)", false, anima::DiffusionBigDelta, "big delta", cmd); + + TCLAP::ValueArg s0ValueArg("s","s0","S0 of DWI (constant value or image)",false,"500","S0 of DWI",cmd); + TCLAP::ValueArg gradsArg("g","grad","Input gradients",true,"","Input gradients",cmd); + TCLAP::ValueArg bvalArg("b","bval","Input b-values",true,"","Input b-values",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef double ScalarType; + typedef std::vector BValueListType; + + typedef vnl_vector_fixed GradientType; + typedef std::vector GradientListType; + + typedef anima::GradientFileReader < GradientType, double > GFReaderType; + GFReaderType gfReader; + gfReader.SetGradientFileName(gradsArg.getValue()); + gfReader.SetBValueBaseString(bvalArg.getValue()); + gfReader.SetSmallDelta(smallDeltaArg.getValue()); + gfReader.SetBigDelta(bigDeltaArg.getValue()); + gfReader.SetGradientIndependentNormalization(false); + gfReader.Update(); + + GradientListType gradients = gfReader.GetGradients(); + + GFReaderType::BValueVectorType gradientStrengths = gfReader.GetGradientStrengths(); + + int numberOfDirections = gradientStrengths.size(); + + typedef anima::MCMImage TInputImageType; + typedef TInputImageType::MCMPointer MCMPointer; + typedef itk::VectorImage TOutputImageType; + + /* Image typedef support */ + typedef TInputImageType::Pointer InputImagePointer; + typedef TOutputImageType::Pointer OutputImagePointer; + + std::cout << "Loading MCM image " << inArg.getValue() << std::endl; + + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(inArg.getValue()); + mcmReader.Update(); + + InputImagePointer mcmImage = mcmReader.GetModelVectorImage(); + + typedef itk::ImageRegionIterator InputIteratorType; + typedef itk::ImageRegionIterator OutputIteratorType; + + OutputImagePointer outImage = TOutputImageType::New(); + outImage->Initialize(); + outImage->SetRegions(mcmImage->GetLargestPossibleRegion()); + outImage->SetOrigin(mcmImage->GetOrigin()); + outImage->SetDirection(mcmImage->GetDirection()); + outImage->SetSpacing(mcmImage->GetSpacing()); + outImage->SetVectorLength(numberOfDirections); + outImage->Allocate(); + + typedef TInputImageType::PixelType InputPixelType; + InputPixelType mcmValue; + + typedef TOutputImageType::PixelType OutputPixelType; + + InputIteratorType mcmIt (mcmImage, mcmImage->GetLargestPossibleRegion()); + OutputIteratorType outIt (outImage, outImage->GetLargestPossibleRegion()); + + OutputPixelType voxelOutputValue(numberOfDirections); + + MCMPointer referenceModel = mcmImage->GetDescriptionModel(); + + typedef itk::Image S0ImageType; + S0ImageType::Pointer s0Image; + try + { + s0Image = anima::readImage (s0ValueArg.getValue()); + } + catch(itk::ExceptionObject &) + { + s0Image = 0; + } + + typedef itk::ImageRegionConstIterator S0ImageIteratorType; + S0ImageIteratorType s0Itr; + + if (s0Image) + s0Itr = S0ImageIteratorType(s0Image, s0Image->GetLargestPossibleRegion()); + + while(!outIt.IsAtEnd()) + { + mcmValue = mcmIt.Get(); + referenceModel->SetModelVector(mcmValue); + + double b0Value = 1; + if (s0Image) + b0Value = s0Itr.Get(); + else + b0Value = boost::lexical_cast (s0ValueArg.getValue()); + + for (int dir = 0;dir < numberOfDirections;++dir) + voxelOutputValue[dir] = b0Value * referenceModel->GetPredictedSignal(smallDeltaArg.getValue(),bigDeltaArg.getValue(), + gradientStrengths[dir],gradients[dir]); + + outIt.Set(voxelOutputValue); + ++outIt; + ++mcmIt; + if (s0Image) + ++s0Itr; + } + + std::cout << "Writing DWI Image : " << resArg.getValue() << std::endl; + anima::writeImage (resArg.getValue(),outImage); + + return 0; +} diff --git a/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/CMakeLists.txt b/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/CMakeLists.txt new file mode 100644 index 000000000..791ca1d17 --- /dev/null +++ b/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/CMakeLists.txt @@ -0,0 +1,38 @@ +if(BUILD_TOOLS) + +project(animaGenerateIsoradiusDDISurface) + +## ############################################################################# +## 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} + AnimaSpecialFunctions + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/animaGenerateIsoradiusDDISurface.cxx b/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/animaGenerateIsoradiusDDISurface.cxx new file mode 100644 index 000000000..82d59b9c5 --- /dev/null +++ b/Anima/diffusion/mcm_tools/generate_isoradius_ddi_surface/animaGenerateIsoradiusDDISurface.cxx @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg RadiusArg("r","radius","Radius",false,1,"Radius",cmd); + TCLAP::ValueArg XDirArg("x","xdir","X coord of orientation",false,1,"X coord of orientation",cmd); + TCLAP::ValueArg YDirArg("y","ydir","Y coord of orientation",false,0,"Y coord of orientation",cmd); + TCLAP::ValueArg ZDirArg("z","zdir","Z coord of orientation",false,0,"Z coord of orientation",cmd); + TCLAP::ValueArg KappaArg("k","kappa","Concentration",false,10,"Concentration",cmd); + TCLAP::ValueArg MeanDiffArg("d","mean-diff","Mean diffusivity",false,0,"Mean diffusivity",cmd); + TCLAP::ValueArg IntraVArg("i","intra-vol","Intra-axonal volume fraction",false,0,"Intra-axonal volume fraction",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + double step = 1e-2; + double inStep = 1e-2; + unsigned int gridSize = (unsigned int)(1.0/step+1); + std::vector theta(gridSize,0); + std::vector phi(gridSize,0); + std::vector inVec(3,0); + std::vector muVec(3,0); + muVec[0] = XDirArg.getValue(); + muVec[1] = YDirArg.getValue(); + muVec[2] = ZDirArg.getValue(); + std::vector integrand((unsigned int)(1.0/inStep+1),0); + vnl_matrix prob; + prob.set_size(gridSize,gridSize); + prob.fill(0); + + double k = KappaArg.getValue(); + double d = MeanDiffArg.getValue(); + double n = IntraVArg.getValue(); + double dFiber = 1.71e-3 / (1.0 - 2.0 * n * anima::xi(k)); + if (d == 0) + d = dFiber; + double r = sqrt(RadiusArg.getValue() * dFiber); + + for (unsigned int i = 0;i < gridSize;++i) + { + theta[i] = i*step*M_PI; + inVec[2] = r * cos(theta[i]); + for (unsigned int j = 0;j < gridSize;++j) + { + phi[j] = j*step*2.0*M_PI; + inVec[0] = r * sin(theta[i]) * cos(phi[j]); + inVec[1] = r * sin(theta[i]) * sin(phi[j]); + prob(i,j) = anima::ComputeSymmetricPDF(inVec, muVec, k, d, n, inStep, integrand); + } + } + + std::cout << "List of Theta values: " << std::endl; + for (unsigned int i = 0;i < gridSize;++i) + std::cout << theta[i] << " "; + std::cout << std::endl; + + std::cout << "List of Phi values: " << std::endl; + for (unsigned int i = 0;i < gridSize;++i) + std::cout << phi[i] << " "; + std::cout << std::endl; + + std::cout << "List of Proba values: " << std::endl; + std::cout << prob << std::endl; + + return 0; +} diff --git a/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/CMakeLists.txt b/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/CMakeLists.txt new file mode 100644 index 000000000..7f29b23d5 --- /dev/null +++ b/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/CMakeLists.txt @@ -0,0 +1,38 @@ +if(BUILD_TOOLS) + +project(animaGetScalarMapFromDDI) + +## ############################################################################# +## 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 + AnimaOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/animaGetScalarMapFromDDI.cxx b/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/animaGetScalarMapFromDDI.cxx new file mode 100644 index 000000000..d03f64b98 --- /dev/null +++ b/Anima/diffusion/mcm_tools/get_scalar_map_from_ddi/animaGetScalarMapFromDDI.cxx @@ -0,0 +1,240 @@ +#include + +#include +#include + +#include + +#include +#include + +#include + +#include + +#include + +enum StringValue +{ + evNotDefined, + evStringValue1, + evStringValue2, + evStringValue3, + evStringValue4, + evStringValue5, + evStringValue6, + evStringValue7, + evStringValue8, + evStringValue9, + evStringValue10, + evStringValue11 +}; + +void InitializeStringArguments(std::map &mapStringValues) +{ + mapStringValues["fa"] = evStringValue1; + mapStringValues["md"] = evStringValue2; + mapStringValues["dpara"] = evStringValue3; + mapStringValues["dperp"] = evStringValue4; + mapStringValues["vr"] = evStringValue5; + mapStringValues["cs"] = evStringValue6; + mapStringValues["kpara"] = evStringValue7; + mapStringValues["kperp"] = evStringValue8; + mapStringValues["od"] = evStringValue9; + mapStringValues["eaf"] = evStringValue10; + mapStringValues["wiso"] = evStringValue11; + + // cl is equivalent to fa under cylindrical symmetry + // cp is zero under cylindrical symmetry +} + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg inArg("i","ddi","DDI volume",true,"","DDI volume",cmd); + TCLAP::ValueArg outArg("o","output","Result scalar image",true,"","result scalar image",cmd); + + TCLAP::SwitchArg averageArg("A","average-val","Compute map at voxel level",cmd,false); + + TCLAP::ValueArg typeArg("t","scalartype","Please choose between\nFractional Anisotropy (FA)\nMean Diffusivity (MD)\nAxial diffusivity (DPARA)\nRadial Diffusivity (DPERP)\nVolume Ratio (VR)\nSpherical Coefficient (CS) \nExtraAxionalFraction (EAF) \n Free water (wiso)" ,true,"","scalar type",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + std::map mapStringValues; + InitializeStringArguments(mapStringValues); + + typedef double ScalarType; + typedef anima::MCMImage TInputImageType; + typedef itk::Image TOutputImageType; + + /** Image typedef support */ + typedef TOutputImageType::Pointer OutputImagePointer; + + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(inArg.getValue()); + mcmReader.Update(); + + typedef itk::ImageRegionIterator InputIteratorType; + typedef itk::ImageRegionIterator OutputIteratorType; + + TInputImageType::Pointer inputImage = mcmReader.GetModelVectorImage(); + anima::MultiCompartmentModel::Pointer mcm = inputImage->GetDescriptionModel(); + + OutputImagePointer outImage = TOutputImageType::New(); + outImage->Initialize(); + outImage->SetRegions(inputImage->GetLargestPossibleRegion()); + outImage->SetOrigin(inputImage->GetOrigin()); + outImage->SetDirection(inputImage->GetDirection()); + outImage->SetSpacing(inputImage->GetSpacing()); + outImage->Allocate(); + + bool hasFreeWater = (mcm->GetCompartment(0)->GetCompartmentType() == anima::FreeWater); + if (!hasFreeWater) + { + std::cerr << "We handle only models with a free water component for now" << std::endl; + return -1; + } + + unsigned int numberOfCompartments = mcm->GetNumberOfCompartments() - 1; + + InputIteratorType ddiItr (inputImage,inputImage->GetLargestPossibleRegion()); + OutputIteratorType outItr (outImage,outImage->GetLargestPossibleRegion()); + + std::transform(typeArg.getValue().begin(),typeArg.getValue().end(),typeArg.getValue().begin(), ::tolower); + + while (!outItr.IsAtEnd()) + { + mcm->SetModelVector(ddiItr.Get()); + double wIso = mcm->GetCompartmentWeight(0); + double dIso = mcm->GetCompartment(0)->GetAxialDiffusivity(); + + double voxelAxialDiffusivity = 0; + double voxelRadialDiffusivity = 0; + double voxelAxialKurtosis = 0; + double voxelRadialKurtosis = 0; + double meanKappa = 0; + double extraAxonalFraction = 0; + + for (unsigned int m = 0;m < numberOfCompartments;++m) + { + double kappaVal = mcm->GetCompartment(m+1)->GetOrientationConcentration(); + double xiVal = anima::xi(kappaVal); + + double dVal = mcm->GetCompartment(m+1)->GetAxialDiffusivity(); + double aVal = mcm->GetCompartment(m+1)->GetExtraAxonalFraction(); + double wVal = mcm->GetCompartmentWeight(m+1); + + voxelAxialDiffusivity += wVal * dVal * (1.0 - 2.0 * aVal * xiVal); + voxelRadialDiffusivity += wVal * dVal * ((1.0 - aVal) / (kappaVal + 1.0) + aVal * xiVal); + + voxelAxialKurtosis += wVal * dVal * dVal * (3.0 * (1.0 - aVal) * (1.0 - aVal) + + 6.0 * aVal * (1.0 - aVal) * anima::jtwo(kappaVal) + + aVal * aVal * anima::jfour(kappaVal)); + voxelRadialKurtosis += wVal * dVal * dVal * (aVal * aVal + 8.0 * aVal * (1.0 - aVal) / (kappaVal + 1.0) + 8.0 * (1.0 - aVal) * (1.0 - aVal) / ((kappaVal + 1.0) * (kappaVal + 1.0)) + - 2.0 * (aVal * aVal + 4.0 * aVal * (1.0 - aVal) / (kappaVal + 1.0)) * anima::jtwo(kappaVal) + + aVal * aVal * anima::jfour(kappaVal)); + + double thrKappa = (wIso > 0.99) ? 0 : kappaVal; + meanKappa += wVal * thrKappa; + + extraAxonalFraction += wVal * aVal; + } + + if (extraAxonalFraction < 1.0e-6) + extraAxonalFraction = 0; + else + extraAxonalFraction /= (1.0 - wIso); + + if (averageArg.isSet()) + { + voxelAxialDiffusivity += wIso * dIso; + voxelRadialDiffusivity += wIso * dIso; + + voxelAxialKurtosis += 3.0 * wIso * dIso * dIso; + voxelRadialKurtosis += 3.0 * wIso * dIso * dIso; + } + else + { + voxelAxialDiffusivity /= (1.0 - wIso); + voxelRadialDiffusivity /= (1.0 - wIso); + + voxelAxialKurtosis /= (1.0 - wIso); + voxelRadialKurtosis /= (1.0 - wIso); + + meanKappa /= (1.0 - wIso); + } + + double scalarVal = 0; + double diffVal = voxelAxialDiffusivity - voxelRadialDiffusivity; + double normVal = std::sqrt(voxelAxialDiffusivity * voxelAxialDiffusivity + 2.0 * voxelRadialDiffusivity * voxelRadialDiffusivity); + + switch (mapStringValues[typeArg.getValue()]) + { + case evStringValue1: + scalarVal = diffVal / normVal; + break; + + case evStringValue2: + scalarVal = (voxelAxialDiffusivity + 2.0 * voxelRadialDiffusivity) / 3.0; + break; + + case evStringValue3: + scalarVal = voxelAxialDiffusivity; + break; + + case evStringValue4: + scalarVal = voxelRadialDiffusivity; + break; + + case evStringValue5: + scalarVal = std::sqrt(voxelAxialDiffusivity) * voxelRadialDiffusivity / std::pow(dIso, 1.5); + break; + + case evStringValue6: + scalarVal = 1.5 * voxelRadialDiffusivity / normVal; + break; + + case evStringValue7: + scalarVal = voxelAxialKurtosis / (voxelAxialDiffusivity * voxelAxialDiffusivity) - 3.0; + break; + + case evStringValue8: + scalarVal = voxelRadialKurtosis / (voxelRadialDiffusivity * voxelRadialDiffusivity) - 3.0; + break; + + case evStringValue9: + scalarVal = 2.0 / M_PI * std::atan(1.0 / meanKappa); + break; + + case evStringValue10: + scalarVal = extraAxonalFraction; + break; + + case evStringValue11: + scalarVal = wIso; + break; + + default: + std::cerr << "Unrecognized scalar type map. Please choose between\nFractional Anisotropy (FA)\nMean Diffusivity (MD)\nAxial diffusivity (DPARA)\nRadial Diffusivity (DPERP)\nVolume Ratio (VR)\nSpherical Coefficient (CS)" << std::endl; + exit(-1); + } + + outItr.Set(scalarVal); + + ++ddiItr; + ++outItr; + } + + anima::writeImage (outArg.getValue(),outImage); + + return 0; +} diff --git a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.h b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.h index 0d10a4531..cb38141d3 100644 --- a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.h +++ b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.h @@ -50,9 +50,11 @@ public itk::ImageToImageFilter< anima::MCMImage , anima::MCMImage MCModelType *GetReferenceOutputModel() {return m_ReferenceOutputModel;} void AddMaskImage(MaskImageType *maskImage) {m_MaskImages.push_back(maskImage);} + + itkSetMacro(DDIAveragingMethod, int) protected: - MCMAverageImagesImageFilter() {} + MCMAverageImagesImageFilter() {m_DDIAveragingMethod = 3;} virtual ~MCMAverageImagesImageFilter() {} bool isZero(const itk::VariableLengthVector &value) const @@ -76,6 +78,7 @@ public itk::ImageToImageFilter< anima::MCMImage , anima::MCMImage std::vector m_ReferenceInputModels; std::vector m_MaskImages; MCModelPointer m_ReferenceOutputModel; + int m_DDIAveragingMethod; }; } // end namespace anima diff --git a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx index 5216e7889..3233b92d9 100644 --- a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx +++ b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx @@ -1,6 +1,8 @@ #pragma once #include "animaMCMAverageImagesImageFilter.h" +#include + #include #include @@ -57,10 +59,33 @@ typename MCMAverageImagesImageFilter ::MCMAveragerPointer MCMAverageImagesImageFilter ::CreateAverager() { - MCMAveragerPointer mcmAverager = anima::MCMWeightedAverager::New(); - mcmAverager->SetOutputModel(m_ReferenceOutputModel); - - return mcmAverager; + MCMAveragerPointer outAverager = nullptr; + + using InternalAveragerPointer = anima::MCMDDIWeightedAverager; + + bool hasDDICompartment = false; + for(int i=0; iGetNumberOfCompartments() && !hasDDICompartment; ++i) + { + hasDDICompartment = m_ReferenceOutputModel->GetCompartment(i)->GetCompartmentType() == DiffusionModelCompartmentType::DDI; + } + + if(hasDDICompartment) + { + InternalAveragerPointer mcmAverager = InternalAveragerType::New(); + mcmAverager->SetOutputModel(this->GetReferenceOutputModel()); + mcmAverager->SetDDIInterpolationMethod(m_DDIAveragingMethod); + + outAverager = mcmAverager.GetPointer(); + } + else + { + MCMAveragerPointer mcmAverager = anima::MCMWeightedAverager::New(); + mcmAverager->SetOutputModel(m_ReferenceOutputModel); + + outAverager = mcmAverager; + } + + return outAverager; } template diff --git a/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt b/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt new file mode 100644 index 000000000..c47ead76d --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt @@ -0,0 +1,39 @@ +if(BUILD_TOOLS AND USE_NLOPT AND NLOPT_FOUND) + +project(animaMCMMergeBlockImages) + +## ############################################################################# +## 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} + AnimaMCMPrivate + AnimaMCM + AnimaOptimizers + ${ITKIO_LIBRARIES} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx b/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx new file mode 100644 index 000000000..ac802c02b --- /dev/null +++ b/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx @@ -0,0 +1,143 @@ +#include +#include +#include + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + typedef anima::MCMImage ImageType; + typedef anima::MCMPrivateFileReader ImageReaderType; + typedef anima::MCMFileWriter ImageWriterType; + + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg inArg("i","inputlist","Input list of chunks of data",true,"","input list",cmd); + TCLAP::ValueArg outArg("o","outputfile","output image",true,"","output image",cmd); + + TCLAP::ValueArg geomArg("g","geometryimage","geometry image",true,"","geometry image",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image GeomImageType; + typedef itk::ImageFileReader GeomReaderType; + GeomReaderType::Pointer geomReader = GeomReaderType::New(); + geomReader->SetFileName(geomArg.getValue()); + geomReader->Update(); + + GeomImageType::SpacingType spacing = geomReader->GetOutput()->GetSpacing(); + GeomImageType::PointType origin = geomReader->GetOutput()->GetOrigin(); + GeomImageType::DirectionType direction = geomReader->GetOutput()->GetDirection(); + GeomImageType::RegionType region = geomReader->GetOutput()->GetLargestPossibleRegion(); + + std::vector fileNames; + std::vector indexes; + + std::ifstream fileList(inArg.getValue().c_str()); + char tmpStr[2048]; + + while (!fileList.eof()) + { + fileList.getline(tmpStr,2048); + while ((strncmp(tmpStr,"",7) != 0)&&(!fileList.eof())) + { + fileList.getline(tmpStr,2048); + } + + if (fileList.eof()) + break; + + fileList.getline(tmpStr,2048); + char fileName[2048]; + GeomImageType::IndexType tmpInd; + while (strncmp(tmpStr,"",8) != 0) + { + if (strncmp(tmpStr,"BLOCK_FILE=",11) == 0) + { + sscanf(tmpStr,"BLOCK_FILE=%s",fileName); + } + else if (strncmp(tmpStr,"STARTING_INDEX=",15) == 0) + { + sscanf(tmpStr,"STARTING_INDEX=%ld %ld %ld",&tmpInd[0],&tmpInd[1],&tmpInd[2]); + } + + fileList.getline(tmpStr,2048); + } + + fileNames.push_back(fileName); + indexes.push_back(tmpInd); + } + + fileList.close(); + + typedef anima::MCMImage MCMImageType; + MCMImageType::Pointer mainData = MCMImageType::New(); + mainData->Initialize(); + + mainData->SetOrigin(origin); + mainData->SetSpacing(spacing); + mainData->SetDirection(direction); + mainData->SetRegions(region); + + for (unsigned int i = 0;i < fileNames.size();++i) + { + std::cout << fileNames[i] << "..." << std::flush; + ImageReaderType tmpRead; + tmpRead.SetFileName(fileNames[i]); + tmpRead.Update(); + + if (i == 0) + { + mainData->SetNumberOfComponentsPerPixel(tmpRead.GetModelVectorImage()->GetNumberOfComponentsPerPixel()); + mainData->Allocate(); + mainData->SetDescriptionModel(tmpRead.GetModelVectorImage()->GetDescriptionModel()); + + itk::VariableLengthVector tmpVecFill(tmpRead.GetModelVectorImage()->GetNumberOfComponentsPerPixel()); + tmpVecFill.Fill(0.0); + itk::ImageRegionIterator tmpDestItr(mainData,mainData->GetLargestPossibleRegion()); + while (!tmpDestItr.IsAtEnd()) + { + tmpDestItr.Set(tmpVecFill); + ++tmpDestItr; + } + } + + MCMImageType::RegionType origRegion = tmpRead.GetModelVectorImage()->GetLargestPossibleRegion(); + MCMImageType::RegionType destRegion = origRegion; + for (unsigned int j = 0;j < MCMImageType::GetImageDimension();++j) + destRegion.SetIndex(j,indexes[i][j]); + + itk::ImageRegionIterator origItr(tmpRead.GetModelVectorImage(),origRegion); + itk::ImageRegionIterator destItr(mainData,destRegion); + itk::VariableLengthVector tmpVec; + + while (!origItr.IsAtEnd()) + { + tmpVec = origItr.Get(); + destItr.Set(tmpVec); + + ++destItr; + ++origItr; + } + + std::cout << " Done..." < +#include +#include +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + typedef anima::MCMImage ImageType; + typedef ImageType::MCMPointer MCMPointer; + typedef anima::MCMPrivateFileReader ImageReaderType; + + TCLAP::CmdLine cmd("Computes various squared distances between estimated and reference multi-tensors models.\n\ + Assumes same number of compartments at each voxel.\n INRIA / IRISA - VisAGeS/Empenn Team", + ' ',ANIMA_VERSION); + + TCLAP::ValueArg testArg("t","test","Test MCM data",true,"","test MCM",cmd); + TCLAP::ValueArg refArg("r","ref","Reference MCM data",true,"","reference MCM",cmd); + + TCLAP::ValueArg outArg("o","out-dist","output squared tensor distance image",true,"","output squared tensor distance image",cmd); + TCLAP::ValueArg outWSEArg("O","out-wse","output squaredWSE image",true,"","output squared WSE image",cmd); + TCLAP::ValueArg outWSEIndArg("W","out-wse-ind","output squared WSE image (independent of tensor pairing)",true,"","output squared WSE independent image",cmd); + TCLAP::ValueArg outL2Arg("l","out-l2","output squared L2 norm image",true,"","output squared L2 norm image",cmd); + + TCLAP::ValueArg maskArg("m","mask","mask image",true,"","mask image",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image MaskImageType; + typedef itk::Image OutputImageType; + typedef OutputImageType::Pointer OutputImagePointer; + + MaskImageType::Pointer maskImage = anima::readImage (maskArg.getValue()); + + ImageReaderType refRead; + refRead.SetFileName(refArg.getValue()); + refRead.Update(); + + ImageType::Pointer refImage = refRead.GetModelVectorImage(); + MCMPointer refMCM = refImage->GetDescriptionModel()->Clone(); + + ImageReaderType testRead; + testRead.SetFileName(testArg.getValue()); + testRead.Update(); + + ImageType::Pointer testImage = testRead.GetModelVectorImage(); + MCMPointer testMCM = testImage->GetDescriptionModel()->Clone(); + + OutputImagePointer wseImage = OutputImageType::New(); + wseImage->Initialize(); + wseImage->SetOrigin(maskImage->GetOrigin()); + wseImage->SetSpacing(maskImage->GetSpacing()); + wseImage->SetDirection(maskImage->GetDirection()); + wseImage->SetRegions(maskImage->GetLargestPossibleRegion()); + wseImage->Allocate(); + wseImage->FillBuffer(0); + + OutputImagePointer wseIndImage = OutputImageType::New(); + wseIndImage->Initialize(); + wseIndImage->SetOrigin(maskImage->GetOrigin()); + wseIndImage->SetSpacing(maskImage->GetSpacing()); + wseIndImage->SetDirection(maskImage->GetDirection()); + wseIndImage->SetRegions(maskImage->GetLargestPossibleRegion()); + wseIndImage->Allocate(); + wseIndImage->FillBuffer(0); + + OutputImagePointer distImage = OutputImageType::New(); + distImage->Initialize(); + distImage->SetOrigin(maskImage->GetOrigin()); + distImage->SetSpacing(maskImage->GetSpacing()); + distImage->SetDirection(maskImage->GetDirection()); + distImage->SetRegions(maskImage->GetLargestPossibleRegion()); + distImage->Allocate(); + distImage->FillBuffer(0); + + OutputImagePointer l2Image = OutputImageType::New(); + l2Image->Initialize(); + l2Image->SetOrigin(maskImage->GetOrigin()); + l2Image->SetSpacing(maskImage->GetSpacing()); + l2Image->SetDirection(maskImage->GetDirection()); + l2Image->SetRegions(maskImage->GetLargestPossibleRegion()); + l2Image->Allocate(); + l2Image->FillBuffer(0); + + itk::ImageRegionIterator maskItr(maskImage, maskImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator wseItr(wseImage, maskImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator wseIndItr(wseIndImage, maskImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator distItr(distImage, maskImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator l2Itr(l2Image, maskImage->GetLargestPossibleRegion()); + + itk::ImageRegionIterator testItr(testImage, maskImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator refItr(refImage, maskImage->GetLargestPossibleRegion()); + + unsigned int numCompartments = testMCM->GetNumberOfCompartments(); + unsigned int numIsoCompartments = testMCM->GetNumberOfIsotropicCompartments(); + unsigned int numNonIsoCompartments = testMCM->GetNumberOfCompartments() - numIsoCompartments; + + typedef anima::BaseCompartment::Matrix3DType TensorType; + std::vector testTensors(numNonIsoCompartments); + std::vector refTensors(numNonIsoCompartments); + vnl_matrix workLogMatrix(3,3), workLogMatrix2(3,3); + std::vector currentPermutation(numNonIsoCompartments); + std::vector testWeights(numNonIsoCompartments); + std::vector refWeights(numNonIsoCompartments); + ImageType::PixelType zeroVec(6); + zeroVec.Fill(0); + zeroVec[0] = std::log(1.5e-3); + zeroVec[2] = std::log(1.5e-3); + zeroVec[5] = std::log(1.5e-3); + + anima::MCML2DistanceComputer::Pointer l2NormComputer = anima::MCML2DistanceComputer::New(); + // Truncates influence of stationary water, put to 0.0 to get original one + l2NormComputer->SetLowPassGaussianSigma(2000.0); + l2NormComputer->SetSquaredDistance(true); + + anima::LogEuclideanTensorCalculator ::Pointer leCalculator = anima::LogEuclideanTensorCalculator ::New(); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + ++maskItr; + ++l2Itr; + ++wseItr; + ++distItr; + ++testItr; + ++refItr; + + continue; + } + + refMCM->SetModelVector(refItr.Get()); + testMCM->SetModelVector(testItr.Get()); + + double isoDist = 0; + double isoWSE = 0; + for (unsigned int i = 0;i < numIsoCompartments;++i) + { + double refWeight = refMCM->GetCompartmentWeight(i); + double testWeight = testMCM->GetCompartmentWeight(i); + isoWSE += (refWeight - testWeight) * (refWeight - testWeight); + + if ((testWeight <= 0)||(refWeight <= 0)) + continue; + + leCalculator->GetTensorLogarithm(refMCM->GetCompartment(i)->GetDiffusionTensor().GetVnlMatrix().as_matrix(), + workLogMatrix); + leCalculator->GetTensorLogarithm(testMCM->GetCompartment(i)->GetDiffusionTensor().GetVnlMatrix().as_matrix(), + workLogMatrix2); + + for (unsigned int j = 0;j < 3;++j) + isoDist += (workLogMatrix(j,j) - workLogMatrix2(j,j)) * (workLogMatrix(j,j) - workLogMatrix2(j,j)); + } + + for (unsigned int i = numIsoCompartments;i < numCompartments;++i) + { + unsigned int index = i - numIsoCompartments; + refWeights[index] = refMCM->GetCompartmentWeight(i); + if (refWeights[index] > 0) + { + leCalculator->GetTensorLogarithm(refMCM->GetCompartment(i)->GetDiffusionTensor().GetVnlMatrix().as_matrix(), + workLogMatrix); + anima::GetVectorRepresentation(workLogMatrix,refTensors[index],6,true); + } + else + refTensors[index] = zeroVec; + + testWeights[index] = testMCM->GetCompartmentWeight(i); + if (testWeights[index] > 0) + { + leCalculator->GetTensorLogarithm(testMCM->GetCompartment(i)->GetDiffusionTensor().GetVnlMatrix().as_matrix(), + workLogMatrix); + anima::GetVectorRepresentation(workLogMatrix,testTensors[index],6,true); + } + else + testTensors[index] = zeroVec; + + currentPermutation[index] = index; + } + + double optimalDist = -1; + double optimalWSE = -1; + do + { + double distPermutation = 0; + for (unsigned int i = 0;i < numNonIsoCompartments;++i) + { + unsigned int index = currentPermutation[i]; + double dist = 0; + for (unsigned int j = 0;j < refTensors[i].GetSize();++j) + dist += (refTensors[i][j] - testTensors[index][j]) * (refTensors[i][j] - testTensors[index][j]); + + distPermutation += dist; + } + + if ((distPermutation < optimalDist)||(optimalDist < 0)) + { + optimalWSE = 0; + for (unsigned int i = 0;i < numNonIsoCompartments;++i) + optimalWSE += (refWeights[i] - testWeights[currentPermutation[i]]) * (refWeights[i] - testWeights[currentPermutation[i]]); + + optimalDist = distPermutation; + } + } while(std::next_permutation(currentPermutation.begin(),currentPermutation.end())); + + double optimalWSEInd = -1; + do + { + double distPermutation = 0; + for (unsigned int i = 0;i < numNonIsoCompartments;++i) + distPermutation += (refWeights[i] - testWeights[currentPermutation[i]]) * (refWeights[i] - testWeights[currentPermutation[i]]); + + if ((distPermutation < optimalWSEInd)||(optimalWSEInd < 0)) + optimalWSEInd = distPermutation; + + } while(std::next_permutation(currentPermutation.begin(),currentPermutation.end())); + + wseItr.Set(optimalWSE + isoWSE); + wseIndItr.Set(optimalWSEInd + isoWSE); + distItr.Set(optimalDist + isoDist); + + double l2Dist = l2NormComputer->ComputeDistance(refMCM,testMCM); + l2Itr.Set(l2Dist); + + ++maskItr; + ++l2Itr; + ++wseItr; + ++wseIndItr; + ++distItr; + ++testItr; + ++refItr; + } + + anima::writeImage(outWSEArg.getValue(),wseImage); + anima::writeImage(outWSEIndArg.getValue(),wseIndImage); + anima::writeImage(outArg.getValue(),distImage); + anima::writeImage(outL2Arg.getValue(),l2Image); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/mcm_tools/test_averaging/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/CMakeLists.txt new file mode 100644 index 000000000..3ccd482b5 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(create_grid_from_pixels) +add_subdirectory(ddi_averaging_on_a_grid) +add_subdirectory(ddi_test_averaging_on_real_value) +add_subdirectory(random_ddi_generator) diff --git a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt new file mode 100644 index 000000000..c8e86055c --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt @@ -0,0 +1,39 @@ +project(animaCreateGridFromPixels) + +## ############################################################################# +## 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} + AnimaMCMPrivate + AnimaMCM + AnimaMCMBase + AnimaOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + + diff --git a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx new file mode 100644 index 000000000..3f6930629 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx @@ -0,0 +1,153 @@ +#include + +#include + +#include +#include +#include + +using namespace anima; + +int main(int argc, char *argv[]) +{ + // Parsing arguments + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + // Setting up parameters + TCLAP::ValueArg inArg("i","input","list of text file",true,"","text file with value of a fascicule",cmd); + TCLAP::ValueArg resArg("o","output","grid of these different value",true,"","grid of these different value",cmd); + TCLAP::ValueArg hArg("", "scale-0", "number of horizontal value", false, 11, "scalar type", cmd); + TCLAP::ValueArg vArg("", "scale-1", "number of vertical value", false, 11, "scalar", cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + exit(-1); + } + + std::ifstream inputFile(inArg.getValue().c_str()); + + if (!inputFile.is_open()) + { + std::cerr << "Please provide usable file with input DDIs" << std::endl; + abort(); + } + + unsigned int numInput = 0; + unsigned int nbPoints0 = hArg.getValue(); + unsigned int nbPoints1 = vArg.getValue(); + + typedef double ScalarType; + std::vector > directions; + vnl_vector onedirection(3); + + std::vector kappa; + std::vector d_fascicule; + std::vector v; + ScalarType temp0, temp1, temp2, temp3, temp4, temp5; + while (!inputFile.eof()) + { + char tmpStr[2048]; + inputFile.getline(tmpStr,2048); + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading pixel " << numInput << ": " << tmpStr << std::endl; + std::ifstream pixelFile(tmpStr); + if (!pixelFile.is_open()) + { + std::cerr << "Please provide usable pixel file" << std::endl; + abort(); + } + pixelFile >> temp0 >> temp1 >> temp2 >> temp3 >> temp4 >> temp5; + onedirection(0) = temp0; + onedirection(1) = temp1; + onedirection(2) = temp2; + directions.push_back(onedirection); + kappa.push_back(temp3); + d_fascicule.push_back(temp4); + v.push_back(temp5); + numInput++; + } + + typedef anima::MCMImage ImageType; + ImageType::IndexType start; + start[0] = 0; + start[1] = 0; + start[2] = 0; + + ImageType::SizeType size; + size[0] = nbPoints0; + size[1] = nbPoints1; + size[2] = 1; + + anima::PrivateMultiCompartmentModelCreator mcmCreator; + mcmCreator.SetCompartmentType(anima::DDI); + mcmCreator.SetNumberOfCompartments(numInput); + mcmCreator.SetModelWithFreeWaterComponent(false); + + anima::MultiCompartmentModel::Pointer mcm = mcmCreator.GetNewMultiCompartmentModel(); + + ImageType::RegionType region(start, size); + ImageType::Pointer image = ImageType::New(); + image->SetRegions(region); + image->SetVectorLength(mcm->GetSize()); + image->Allocate(); + + ImageType::IndexType pixelIndex; + pixelIndex[2] = 0; + + std::vector weight(4); + ScalarType scale0 = 1.0/(nbPoints0 - 1.0); + ScalarType scale1 = 1.0/(nbPoints1 - 1.0); + + ImageType::PixelType voxelOutputValue(mcm->GetSize()); + voxelOutputValue.Fill(0); + mcm->SetModelVector(voxelOutputValue); + + for (unsigned int k = 0; k < numInput; k++) + { + anima::BaseCompartment *workCompartment = mcm->GetCompartment(k); + + workCompartment->SetAxialDiffusivity(d_fascicule[k]); + workCompartment->SetOrientationConcentration(kappa[k]); + workCompartment->SetExtraAxonalFraction(v[k]); + + anima::TransformCartesianToSphericalCoordinates(directions[k],directions[k]); + workCompartment->SetOrientationTheta(directions[k][0]); + workCompartment->SetOrientationPhi(directions[k][1]); + } + + for (unsigned int i = 0; i < nbPoints0; i++) + { + for (unsigned int j = 0; j < nbPoints1; j++) + { + pixelIndex[0] = i; + pixelIndex[1] = j; + + weight[0] = (1 - scale0*i) * (1 - scale1*j); + weight[1] = (1 - scale0*i) * scale1*j; + weight[2] = scale0*i * (1 - scale1*j); + weight[3] = scale0 * i * scale1 * j; + + mcm->SetCompartmentWeights(weight); + voxelOutputValue = mcm->GetModelVector(); + + //if (((i == 0)&&((j == 0)||(j == nbPoints1 - 1))) || ((i == nbPoints0 - 1)&&((j == 0)||(j == nbPoints1 - 1)))) + image->SetPixel(pixelIndex, voxelOutputValue); + } + } + + image->SetDescriptionModel(mcm); + anima::MCMFileWriter mcmWriter; + mcmWriter.SetFileName(resArg.getValue()); + mcmWriter.SetInputImage(image); + + mcmWriter.Update(); + + return 0; +} diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt new file mode 100644 index 000000000..28a5b29e1 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt @@ -0,0 +1,38 @@ +project(animaDDIAveragingOnAGrid) + +## ############################################################################# +## 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} + AnimaMCMPrivate + AnimaMCM + AnimaMCMBase + AnimaOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx new file mode 100644 index 000000000..83ced300a --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx @@ -0,0 +1,152 @@ +#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","inputddi","list of text file",true,"","text file with value of a fascicule",cmd); + TCLAP::ValueArg resArg("o","output","extrapolate grid from input pixels",true,"","extrapolate grid from input pixels",cmd); + TCLAP::ValueArg hArg("", "scale-0", "number of horizontal value", false, 11, "scalar type", cmd); + TCLAP::ValueArg vArg("", "scale-1", "number of vertical value", false, 11, "scalar", cmd); + TCLAP::ValueArg methodArg("m", "method", "method use to average, Classic : 0, Tensor : 1, logVMF : 2, covarianceAnalytic : 3", true , 3, "method use to average", cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + std::ifstream inputFile(inArg.getValue().c_str()); + + if (!inputFile.is_open()) + { + std::cerr << "Please provide usable file with input DDIs" << std::endl; + abort(); + } + + int method = methodArg.getValue(); + double nbPoints0 = hArg.getValue(); + double nbPoints1 = vArg.getValue(); + unsigned int numInput = 0; + + typedef double ScalarType; + std::vector > directions; + vnl_vector onedirection(3); + std::vector kappa; + std::vector d_fascicule; + std::vector v; + + ScalarType temp0, temp1, temp2, temp3, temp4, temp5; + while (!inputFile.eof()) + { + char tmpStr[2048]; + inputFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading pixel " << numInput << ": " << tmpStr << std::endl; + + std::ifstream pixelFile(tmpStr); + + if (!pixelFile.is_open()) + { + std::cerr << "Please provide usable pixel file" << std::endl; + abort(); + } + pixelFile >> temp0 >> temp1 >> temp2 >> temp3 >> temp4 >> temp5; + + onedirection(0) = temp0; + onedirection(1) = temp1; + onedirection(2) = temp2; + directions.push_back(onedirection); + kappa.push_back(temp3); + d_fascicule.push_back(temp4); + v.push_back(temp5); + + numInput++; + } + + typedef anima::MCMImage ImageType; + ImageType::IndexType start; + start[0] = 0; + start[1] = 0; + start[2] = 0; + + ImageType::SizeType size; + size[0] = nbPoints0; + size[1] = nbPoints1; + size[2] = 1; + + anima::PrivateMultiCompartmentModelCreator mcmCreator; + mcmCreator.SetCompartmentType(anima::DDI); + mcmCreator.SetNumberOfCompartments(1); + mcmCreator.SetModelWithFreeWaterComponent(false); + + anima::MultiCompartmentModel::Pointer mcm = mcmCreator.GetNewMultiCompartmentModel(); + + ImageType::RegionType region(start, size); + ImageType::Pointer image = ImageType::New(); + image->SetRegions(region); + image->SetVectorLength(mcm->GetSize()); + image->Allocate(); + + ImageType::IndexType pixelIndex; + pixelIndex[2] = 0; + + ScalarType scale0 = 1.0 / (nbPoints0 - 1); + ScalarType scale1 = 1.0 / (nbPoints1 - 1); + + ImageType::PixelType voxelOutputValue(mcm->GetSize()); + std::vector weights(4,1); + std::vector outputWeights(1,1); + mcm->SetCompartmentWeights(outputWeights); + + for (int i = 0; i < nbPoints0; i++) + { + for (int j = 0; j < nbPoints1; j++) + { + pixelIndex[0] = i; + pixelIndex[1] = j; + weights[0] = (1 - scale0*i) * (1 - scale1*j); + weights[1] = (1 - scale0*i) * scale1*j; + weights[2] = scale0*i * (1 - scale1*j); + weights[3] = scale0 * i * scale1 * j; + + double averageNu = 0, averageDiffusivity = 0, averageKappa = 0, averageWeight = 1; + + anima::DDIAveraging(v,d_fascicule,kappa,directions,weights,method,averageNu,averageDiffusivity,averageKappa,onedirection,averageWeight); + + anima::BaseCompartment *workCompartment = mcm->GetCompartment(0); + workCompartment->SetAxialDiffusivity(averageDiffusivity); + workCompartment->SetOrientationConcentration(averageKappa); + workCompartment->SetExtraAxonalFraction(averageNu); + anima::TransformCartesianToSphericalCoordinates(onedirection,onedirection); + workCompartment->SetOrientationTheta(onedirection[0]); + workCompartment->SetOrientationPhi(onedirection[1]); + + voxelOutputValue = mcm->GetModelVector(); + image->SetPixel(pixelIndex, voxelOutputValue); + } + } + + image->SetDescriptionModel(mcm); + anima::MCMFileWriter mcmWriter; + mcmWriter.SetFileName(resArg.getValue()); + mcmWriter.SetInputImage(image); + + mcmWriter.Update(); + + return 0; +} diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt new file mode 100644 index 000000000..54c9167c0 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt @@ -0,0 +1,38 @@ +project(animaDDITestAveragingOnRealValue) + +## ############################################################################# +## 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} + AnimaMCMPrivate + AnimaMCMPrivateBase + AnimaMCM + AnimaMCMBase + AnimaOptimizers + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx new file mode 100644 index 000000000..518bfa353 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx @@ -0,0 +1,94 @@ +#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","inputddi","DDI image",true,"","DDI image",cmd); + TCLAP::ValueArg resArg("o","output", "Extrapolation of input DDI image",true,"","result DDI image",cmd); + TCLAP::ValueArg stepArg("", "step", "interpolation step (default : 2)", false, 2, "step", cmd); + TCLAP::ValueArg methodArg("m", "method", "method use to average, Classic : 0, Tensor : 1, log VMF : 2, covarianceAnalytic : 3", true , 3, "method use to average", 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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + anima::DDITestAveragingOnRealValueImageFilter::Pointer mainFilter = anima::DDITestAveragingOnRealValueImageFilter::New(); + + typedef anima::MCMImage ImageType; + typedef anima::MCMPrivateFileReader MCMReaderType; + typedef anima::MCMFileWriter MCMWriterType; + + MCMReaderType mcmReader; + mcmReader.SetFileName(inArg.getValue()); + mcmReader.Update(); + + anima::MultiCompartmentModel::Pointer inputModel = mcmReader.GetModelVectorImage()->GetDescriptionModel(); + + mainFilter->SetInput(mcmReader.GetModelVectorImage()); + mainFilter->SetStep(stepArg.getValue()); + mainFilter->SetMethod(methodArg.getValue()); + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + + anima::PrivateMultiCompartmentModelCreator mcmCreator; + mcmCreator.SetModelWithFreeWaterComponent(false); + mcmCreator.SetModelWithRestrictedWaterComponent(false); + mcmCreator.SetModelWithStaniszComponent(false); + mcmCreator.SetModelWithStationaryWaterComponent(false); + + for (unsigned int i = 0;i < inputModel->GetNumberOfIsotropicCompartments();++i) + { + switch (inputModel->GetCompartment(i)->GetCompartmentType()) + { + case anima::FreeWater: + mcmCreator.SetModelWithFreeWaterComponent(true); + break; + + case anima::IsotropicRestrictedWater: + mcmCreator.SetModelWithRestrictedWaterComponent(true); + break; + + case anima::Stanisz: + mcmCreator.SetModelWithStaniszComponent(true); + break; + + case anima::StationaryWater: + mcmCreator.SetModelWithStationaryWaterComponent(true); + break; + + default: + std::cerr << "Unhandled isotropic compartment" << std::endl; + return EXIT_FAILURE; + } + } + + mcmCreator.SetCompartmentType(inputModel->GetCompartment(inputModel->GetNumberOfIsotropicCompartments())->GetCompartmentType()); + mcmCreator.SetNumberOfCompartments(inputModel->GetNumberOfCompartments() - inputModel->GetNumberOfIsotropicCompartments()); + + anima::MultiCompartmentModel::Pointer outputReferenceModel = mcmCreator.GetNewMultiCompartmentModel(); + + mainFilter->SetReferenceOutputModel(outputReferenceModel); + mainFilter->Update(); + + MCMWriterType mcmWriter; + mcmWriter.SetInputImage(mainFilter->GetOutput()); + + mcmWriter.SetFileName(resArg.getValue()); + mcmWriter.Update(); + + return EXIT_SUCCESS; +} + diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx new file mode 100644 index 000000000..4efb07553 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx @@ -0,0 +1,112 @@ +#include "animaDDITestAveragingOnRealValueImageFilter.h" + +#include +#include + +#include +#include + +namespace anima +{ + +void DDITestAveragingOnRealValueImageFilter::BeforeThreadedGenerateData() +{ + this->Superclass::BeforeThreadedGenerateData(); + + InputImageType *input = const_cast (this->GetInput()); + MCModelPointer model = input->GetDescriptionModel(); + unsigned int numIsoCompartments = model->GetNumberOfIsotropicCompartments(); + + if (model->GetNumberOfCompartments() > numIsoCompartments) + { + if (model->GetCompartment(numIsoCompartments)->GetCompartmentType() != anima::DDI) + itkExceptionMacro("DDI averaging only supports models with DDI compartments"); + } + + m_ReferenceInputModel = model; + + this->GetOutput()->SetDescriptionModel(m_ReferenceOutputModel); +} + + +void DDITestAveragingOnRealValueImageFilter::SetReferenceOutputModel(MCModelPointer &model) +{ + if (model->GetNumberOfCompartments() > model->GetNumberOfIsotropicCompartments()) + { + if (model->GetCompartment(model->GetNumberOfIsotropicCompartments())->GetCompartmentType() != anima::DDI) + itkExceptionMacro("DDI averaging only supports models with DDI compartments"); + } + + m_ReferenceOutputModel = model; +} + + +void DDITestAveragingOnRealValueImageFilter::DynamicThreadedGenerateData(const InputRegionType ®ion) +{ + typedef itk::ImageRegionConstIteratorWithIndex InputIteratorType; + typedef itk::ImageRegionIterator OutputIteratorType; + + InputIteratorType inputIterator(this->GetInput(), region); + OutputIteratorType outputIterator (this->GetOutput(), region); + + InputImageType::SizeType totalSize = region.GetSize(); + + typedef anima::MCMPrivateWeightedAverager::Pointer MCMAveragerPointer; + MCMAveragerPointer mcmAverager = anima::MCMPrivateWeightedAverager::New(); + mcmAverager->SetDDIInterpolationMethod(m_Method); + mcmAverager->SetOutputModel(m_ReferenceOutputModel); + + PixelType voxelOutputValue(m_ReferenceOutputModel->GetSize()); + + while (!inputIterator.IsAtEnd()) + { + voxelOutputValue.Fill(0.0); + InputIndexType inputIndex = inputIterator.GetIndex(); + std::vector variableIndex; + std::vector wPosition; + unsigned int nbVoxels = 0; + + // Compute which voxels we interpolate and their weights. + anima::ComputeVoxelWeights(inputIndex, variableIndex, wPosition, totalSize, m_Step, nbVoxels); + + if (nbVoxels == 0) + { + outputIterator.Set(inputIterator.Get()); + + ++inputIterator; + ++outputIterator; + + continue; + } + + std::vector workInputWeights(nbVoxels,0.0); + + std::vector workInputModels(nbVoxels); + for (unsigned int i = 0;i < nbVoxels;++i) + workInputModels[i] = m_ReferenceInputModel->Clone(); + + //Pull all fascicles (and free water) with corresponding weights in one vector + for (int i = 0;i < nbVoxels;++i) + { + PixelType ddiValue = this->GetInput()->GetPixel(variableIndex[i]); + workInputModels[i]->SetModelVector(ddiValue); + workInputWeights[i] = 1.0; + } + + mcmAverager->SetInputModels(workInputModels); + mcmAverager->SetInputWeights(workInputWeights); + + // weights are normalized inside averager + mcmAverager->Update(); + + voxelOutputValue = mcmAverager->GetOutputModel()->GetModelVector(); + + //Set output value then upgrade iterators + outputIterator.Set(voxelOutputValue); + + ++inputIterator; + ++outputIterator; + } +} + +} // end namespace anima diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.h b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.h new file mode 100644 index 000000000..911b64595 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include +#include + +namespace anima +{ + +class DDITestAveragingOnRealValueImageFilter : +public itk::ImageToImageFilter< anima::MCMImage , anima::MCMImage > +{ +public: + + /** Standard class type def */ + typedef DDITestAveragingOnRealValueImageFilter Self; + typedef anima::MCMImage InputImageType; + typedef anima::MCMImage OutputImageType; + 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(DDITestAveragingOnRealValueImageFilter, ImageToImageFilter) + + typedef InputImageType::ConstPointer InputImagePointer; + typedef OutputImageType::Pointer OutputImagePointer; + + typedef InputImageType::RegionType InputRegionType; + typedef InputImageType::IndexType InputIndexType; + typedef InputImageType::PixelType PixelType; + + // Multi-compartment models typedefs + typedef anima::MultiCompartmentModel MCModelType; + typedef MCModelType::Pointer MCModelPointer; + + itkSetMacro(Step, int) + itkSetMacro(Method, int) + + void SetReferenceOutputModel(MCModelPointer &model); + +protected: + DDITestAveragingOnRealValueImageFilter () + { + m_Step = 2; + } + virtual ~DDITestAveragingOnRealValueImageFilter () {} + + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const InputRegionType ®ion) ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(DDITestAveragingOnRealValueImageFilter); + + int m_Step; + int m_Method; + + MCModelPointer m_ReferenceInputModel; + MCModelPointer m_ReferenceOutputModel; +}; + +} diff --git a/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/CMakeLists.txt new file mode 100644 index 000000000..ba69d1ac3 --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/CMakeLists.txt @@ -0,0 +1,35 @@ +project(animaRandomDDIGenerator) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + + diff --git a/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/animaRandomDDIGenerator.cxx b/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/animaRandomDDIGenerator.cxx new file mode 100644 index 000000000..8f3ff513b --- /dev/null +++ b/Anima/diffusion/mcm_tools/test_averaging/random_ddi_generator/animaRandomDDIGenerator.cxx @@ -0,0 +1,91 @@ +#include +#include +#include + +#include +#include +#include + +#include + +using namespace anima; + +int main(int argc, char *argv[]) +{ + + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg firstNameArg("f","firstName","name of the output file with .txt",true,"","name of the output file with the name of 4 files",cmd); + TCLAP::ValueArg pixelNameArg("s","secondName","name of the output files without .txt",true,"","name of the 4 output files",cmd); + + TCLAP::ValueArg kappaLow("","kappaLow","kappa lwo bound",false,0,"kappa lwo bound",cmd); + TCLAP::ValueArg kappaHigh("","kappaHigh","kappa high bound",false,20,"kappa high bound",cmd); + + TCLAP::ValueArg vLow("","vLow","v lwo bound",false,0,"v lwo bound",cmd); + TCLAP::ValueArg vHigh("","vHigh","v high bound",false,1,"v high bound",cmd); + + TCLAP::ValueArg dLow("","dLow","d lwo bound",false,0.0005,"d lwo bound",cmd); + TCLAP::ValueArg dHigh("","dHigh","d high bound",false,0.005,"d high bound",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + double kappa, v, d, theta, phi; + vnl_vector angles(3, 0), direction(3, 0); + + srand(time(NULL)); + + for (int i=0; i<4; i++) + { + + kappa = rand()/(double)RAND_MAX * (kappaHigh.getValue() - kappaLow.getValue()) + kappaLow.getValue(); + v = rand()/(double)RAND_MAX * (vHigh.getValue() - vLow.getValue()) + vLow.getValue(); + d = rand()/(double)RAND_MAX * (dHigh.getValue() - dLow.getValue()) + dLow.getValue(); + theta = rand()/(double)RAND_MAX * 2 * M_PI; + phi = rand()/(double)RAND_MAX * M_PI; + + angles(0) = phi; + angles(1) = theta; + angles(2) = 1; + TransformSphericalToCartesianCoordinates(angles, direction); + + std::stringstream convertNumber; + convertNumber << i; + + std::string namePixel = pixelNameArg.getValue() + convertNumber.str() + ".txt"; + std::ofstream fichierPixel; + fichierPixel.open(namePixel.c_str()); + + if (fichierPixel) + { + fichierPixel << direction(0) << std::endl; + fichierPixel << direction(1) << std::endl; + fichierPixel << direction(2) << std::endl; + fichierPixel << kappa << std::endl; + fichierPixel << d << std::endl; + fichierPixel << v << std::endl; + fichierPixel << 1.0 << std::endl; + + fichierPixel.close(); + } + else + std::cerr << "Erreur à l'ouverture !" << std::endl; + } + + std::ofstream fichier; + fichier.open(firstNameArg.getValue().c_str()); + + for (int i=0; i<4; i++) + fichier << pixelNameArg.getValue() << i << ".txt" << std::endl; + + fichier.close(); + + return 0; +} diff --git a/Anima/diffusion/tractography/CMakeLists.txt b/Anima/diffusion/tractography/CMakeLists.txt index c4abf2f34..a57df0825 100644 --- a/Anima/diffusion/tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/CMakeLists.txt @@ -55,5 +55,7 @@ add_subdirectory(dti_tractography) add_subdirectory(fibers_counter) add_subdirectory(fibers_filterer) add_subdirectory(odf_probabilistic_tractography) +add_subdirectory(mcm_probabilistic_tractography) +add_subdirectory(mcm__tractography) endif() #USE_VTK AND VTK_FOUND diff --git a/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx new file mode 100644 index 000000000..a2f9c0c7a --- /dev/null +++ b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.cxx @@ -0,0 +1,344 @@ +#include "animaMCMProbabilisticTractographyImageFilter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace anima +{ + +MCMProbabilisticTractographyImageFilter::MCMProbabilisticTractographyImageFilter(): BaseProbabilisticTractographyImageFilter() +{ + m_FAThreshold = 0.5; + + // Useless here, defined on the fly as MCM image is set + this->SetModelDimension(1); + m_IsotropicThreshold = 0.8; +} + +MCMProbabilisticTractographyImageFilter::~MCMProbabilisticTractographyImageFilter() +{ +} + +MCMProbabilisticTractographyImageFilter::InterpolatorType * +MCMProbabilisticTractographyImageFilter::GetModelInterpolator() +{ + typedef anima::MCMLinearInterpolateImageFunction MCMInterpolatorType; + typedef MCMInterpolatorType::Pointer MCMInterpolatorPointer; + + MCMInterpolatorPointer internalInterpolator = MCMInterpolatorType::New(); + internalInterpolator->SetInputImage(this->GetInputModelImage()); + + MCModelPointer tmpMCM = this->GetInputModelImage()->GetDescriptionModel()->Clone(); + internalInterpolator->SetReferenceOutputModel(tmpMCM); + + internalInterpolator->Register(); + return internalInterpolator; +} + +MCMProbabilisticTractographyImageFilter::Vector3DType +MCMProbabilisticTractographyImageFilter::ProposeNewDirection(Vector3DType &oldDirection, VectorType &modelValue, + Vector3DType &sampling_direction, double &log_prior, + double &log_proposal, std::mt19937 &random_generator, + unsigned int threadId) +{ + double chosenKappa = 0; + + m_WorkModels[threadId]->SetModelVector(modelValue); + unsigned int numIsoCompartments = m_WorkModels[threadId]->GetNumberOfIsotropicCompartments(); + unsigned int numDirs = m_WorkModels[threadId]->GetNumberOfCompartments(); + bool is2d = this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] <= 1; + + ListType mixtureWeights; + ListType kappaValues; + DirectionVectorType maximaMCM; + + unsigned int effectiveNumDirs = 0; + Vector3DType direction; + for (unsigned int i = numIsoCompartments;i < numDirs;++i) + { + double weight = m_WorkModels[threadId]->GetCompartmentWeight(i); + if (weight == 0) + continue; + + anima::BaseCompartment *workCompartment = m_WorkModels[threadId]->GetCompartment(i); + double fa = workCompartment->GetApparentFractionalAnisotropy(); + + if (fa < m_FAThreshold) + continue; + + anima::TransformSphericalToCartesianCoordinates(workCompartment->GetOrientationTheta(),workCompartment->GetOrientationPhi(), + 1.0,direction); + + if (is2d) + { + direction[2] = 0; + direction.Normalize(); + } + + if (anima::ComputeScalarProduct(oldDirection, direction) < 0) + direction *= -1; + + maximaMCM.push_back(direction); + kappaValues.push_back(GetKappaFromFA(fa)); + mixtureWeights.push_back(weight); + + ++effectiveNumDirs; + } + + if (effectiveNumDirs == 0) + { + maximaMCM.push_back(oldDirection); + kappaValues.push_back(this->GetKappaOfPriorDistribution()); + mixtureWeights.push_back(1.0); + } + + std::discrete_distribution<> dist(mixtureWeights.begin(),mixtureWeights.end()); + unsigned int chosenDirection = dist(random_generator); + sampling_direction = maximaMCM[chosenDirection]; + chosenKappa = kappaValues[chosenDirection]; + + bool nullOld = true; + for (unsigned int i = 0;i < 3;++i) + { + if (sampling_direction[i] != 0) + { + nullOld = false; + break; + } + } + + if (nullOld) + 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); + + if (is2d) + { + resVec[InputModelImageType::ImageDimension - 1] = 0; + resVec.Normalize(); + } + + 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())); + + 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]); + sumWeights += mixtureWeights[i]; + } + + log_proposal = anima::safe_log(log_proposal / sumWeights); + } + + if (anima::ComputeScalarProduct(oldDirection, resVec) < 0) + resVec *= -1; + + return resVec; +} + +MCMProbabilisticTractographyImageFilter::Vector3DType MCMProbabilisticTractographyImageFilter::InitializeFirstIterationFromModel(Vector3DType &colinearDir, VectorType &modelValue, + unsigned int threadId) +{ + Vector3DType resVec, tmpVec; + bool is2d = (this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] == 1); + + m_WorkModels[threadId]->SetModelVector(modelValue); + unsigned int numIsoCompartments = m_WorkModels[threadId]->GetNumberOfIsotropicCompartments(); + unsigned int numberCompartments = m_WorkModels[threadId]->GetNumberOfCompartments(); + + double maxVal = 0; + + switch (this->GetInitialDirectionMode()) + { + case Colinear: + { + for (unsigned int i = numIsoCompartments;i < numberCompartments;++i) + { + if (m_WorkModels[threadId]->GetCompartmentWeight(i) == 0) + continue; + + anima::TransformSphericalToCartesianCoordinates(m_WorkModels[threadId]->GetCompartment(i)->GetOrientationTheta(), + m_WorkModels[threadId]->GetCompartment(i)->GetOrientationPhi(), + 1.0,tmpVec); + + double tmpVal = anima::ComputeScalarProduct(colinearDir, tmpVec); + + if (tmpVal < 0) + { + tmpVec *= -1; + tmpVal *= -1; + } + + if (tmpVal > maxVal) + { + resVec = tmpVec; + maxVal = tmpVal; + } + } + + break; + } + + case Weight: + default: + { + unsigned int indexMax = numIsoCompartments; + maxVal = m_WorkModels[threadId]->GetCompartmentWeight(numIsoCompartments); + + for (unsigned int i = numIsoCompartments+1;i < numberCompartments;++i) + { + if (m_WorkModels[threadId]->GetCompartmentWeight(i) >= maxVal) + { + maxVal = m_WorkModels[threadId]->GetCompartmentWeight(i); + indexMax = i; + } + } + + anima::TransformSphericalToCartesianCoordinates(m_WorkModels[threadId]->GetCompartment(indexMax)->GetOrientationTheta(), + m_WorkModels[threadId]->GetCompartment(indexMax)->GetOrientationPhi(), + 1.0,resVec); + + double tmpVal = anima::ComputeScalarProduct(colinearDir, resVec); + + if (tmpVal < 0) + resVec *= -1; + + break; + } + } + + if (maxVal == 0) + resVec = colinearDir; + + if (is2d) + { + resVec[2] = 0; + resVec.Normalize(); + } + + return resVec; +} + +bool MCMProbabilisticTractographyImageFilter::CheckModelProperties(double estimatedB0Value, double estimatedNoiseValue, VectorType &modelValue, + unsigned int threadId) +{ + // Prevent fibers from going outside of brain mask + if (estimatedB0Value < 10.0) + return false; + + // SNR too high means not in white matter anymore + double snr = estimatedB0Value / std::sqrt(estimatedNoiseValue); + if (snr > 60.0) + return false; + + // if all fixels are damaged, stop extending fibers + unsigned int numIsoCompartments = this->GetInputModelImage()->GetDescriptionModel()->GetNumberOfIsotropicCompartments(); + unsigned int numberOfFixels = this->GetInputModelImage()->GetDescriptionModel()->GetNumberOfCompartments(); + bool allFixelsDamaged = true; + + m_WorkModels[threadId]->SetModelVector(modelValue); + for (unsigned int i = numIsoCompartments;i < numberOfFixels;++i) + { + if (m_WorkModels[threadId]->GetCompartment(i)->GetExtraAxonalFraction() < m_IsotropicThreshold) + { + allFixelsDamaged = false; + break; + } + } + + if (allFixelsDamaged) + return false; + + // if free water is too important, stop fibers + double isotropicProportion = 0; + for (unsigned int i = 0;i < numIsoCompartments;++i) + isotropicProportion += m_WorkModels[threadId]->GetCompartmentWeight(i); + + if (isotropicProportion > m_IsotropicThreshold) + return false; + + return true; +} + +double MCMProbabilisticTractographyImageFilter::ComputeLogWeightUpdate(double b0Value, double noiseValue, Vector3DType &newDirection, VectorType &modelValue, + double &log_prior, double &log_proposal, unsigned int threadId) +{ + double logLikelihood = 0.0; + Vector3DType tmpVec; + + bool is2d = (this->GetInputModelImage()->GetLargestPossibleRegion().GetSize()[2] == 1); + + MCModelPointer workModel = m_WorkModels[threadId]; + workModel->SetModelVector(modelValue); + + double concentrationParameter = b0Value / std::sqrt(noiseValue); + + bool oneTested = false; + for (unsigned int i = workModel->GetNumberOfIsotropicCompartments();i < workModel->GetNumberOfCompartments();++i) + { + if (workModel->GetCompartmentWeight(i) == 0) + continue; + + anima::TransformSphericalToCartesianCoordinates(workModel->GetCompartment(i)->GetOrientationTheta(), + workModel->GetCompartment(i)->GetOrientationPhi(), + 1.0,tmpVec); + + if (is2d) + { + tmpVec[2] = 0.0; + anima::Normalize(tmpVec,tmpVec); + } + + double tmpVal = std::log(anima::EvaluateWatsonPDF(tmpVec, newDirection, concentrationParameter)); + if ((tmpVal > logLikelihood)||(!oneTested)) + { + logLikelihood = tmpVal; + oneTested = true; + } + } + + double resVal = logLikelihood + log_prior - log_proposal; + return resVal; +} + +void MCMProbabilisticTractographyImageFilter::ComputeModelValue(InterpolatorPointer &modelInterpolator, ContinuousIndexType &index, VectorType &modelValue) +{ + modelValue.SetSize(this->GetModelDimension()); + modelValue.Fill(0.0); + + if (modelInterpolator->IsInsideBuffer(index)) + modelValue = modelInterpolator->EvaluateAtContinuousIndex(index); +} + +void MCMProbabilisticTractographyImageFilter::SetKappaPolynomialCoefficients(ListType &coefs) +{ + m_KappaPolynomialCoefficients = coefs; +} + +double MCMProbabilisticTractographyImageFilter::GetKappaFromFA(double FA) +{ + double resVal = 0.0; + for (unsigned int i = 0;i < m_KappaPolynomialCoefficients.size();++i) + resVal += m_KappaPolynomialCoefficients[i] * std::pow(FA, (double)i); + + return 0.5 * resVal; +} + +} // end of namespace anima diff --git a/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.h b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.h new file mode 100644 index 000000000..a51905f30 --- /dev/null +++ b/Anima/diffusion/tractography/animaMCMProbabilisticTractographyImageFilter.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +#include "AnimaTractographyExport.h" + +namespace anima +{ + +class ANIMATRACTOGRAPHY_EXPORT MCMProbabilisticTractographyImageFilter : public BaseProbabilisticTractographyImageFilter < anima::MCMImage > +{ +public: + /** SmartPointer typedef support */ + typedef MCMProbabilisticTractographyImageFilter Self; + typedef BaseProbabilisticTractographyImageFilter < anima::MCMImage > Superclass; + + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + itkNewMacro(Self) + itkTypeMacro(MCMProbabilisticTractographyImageFilter,BaseProbabilisticTractographyImageFilter) + + typedef anima::MultiCompartmentModel MCModelType; + typedef MCModelType::Pointer MCModelPointer; + + void SetInputModelImage(InputModelImageType *image) ITK_OVERRIDE + { + this->Superclass::SetInputModelImage(image); + this->SetModelDimension(image->GetDescriptionModel()->GetSize()); + + m_WorkModels.resize(itk::MultiThreaderBase::GetGlobalMaximumNumberOfThreads()); + for (unsigned int i = 0;i < m_WorkModels.size();++i) + m_WorkModels[i] = image->GetDescriptionModel()->Clone(); + } + + itkSetMacro(FAThreshold,double) + itkSetMacro(IsotropicThreshold,double) + + void SetKappaPolynomialCoefficients(std::vector &coefs); + + InterpolatorType *GetModelInterpolator() ITK_OVERRIDE; + +protected: + MCMProbabilisticTractographyImageFilter(); + virtual ~MCMProbabilisticTractographyImageFilter(); + + virtual Vector3DType ProposeNewDirection(Vector3DType &oldDirection, VectorType &modelValue, + Vector3DType &sampling_direction, double &log_prior, + double &log_proposal, std::mt19937 &random_generator, + unsigned int threadId) ITK_OVERRIDE; + + virtual double ComputeLogWeightUpdate(double b0Value, double noiseValue, Vector3DType &newDirection, VectorType &modelValue, + double &log_prior, double &log_proposal, unsigned int threadId) ITK_OVERRIDE; + + virtual void ComputeModelValue(InterpolatorPointer &modelInterpolator, ContinuousIndexType &index, VectorType &modelValue) ITK_OVERRIDE; + + virtual Vector3DType InitializeFirstIterationFromModel(Vector3DType &colinearDir, VectorType &modelValue, + unsigned int threadId) ITK_OVERRIDE; + + virtual bool CheckModelProperties(double estimatedB0Value, double estimatedNoiseValue, VectorType &modelValue, + unsigned int threadId) ITK_OVERRIDE; + + double GetKappaFromFA(double FA); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(MCMProbabilisticTractographyImageFilter); + + std::vector m_WorkModels; + + ListType m_KappaPolynomialCoefficients; + + double m_FAThreshold; + double m_IsotropicThreshold; +}; + +} // end of namespace anima diff --git a/Anima/diffusion/tractography/animaMCMTractographyImageFilter.cxx b/Anima/diffusion/tractography/animaMCMTractographyImageFilter.cxx new file mode 100644 index 000000000..43720d278 --- /dev/null +++ b/Anima/diffusion/tractography/animaMCMTractographyImageFilter.cxx @@ -0,0 +1,159 @@ +#include "animaMCMTractographyImageFilter.h" +#include + +namespace anima +{ + +MCMTractographyImageFilter::MCMTractographyImageFilter() +{ + m_StopIsoWeightThreshold = 0.8; + m_MinimalDirectionRelativeWeight = 0.2; +} + +MCMTractographyImageFilter::~MCMTractographyImageFilter() +{ +} + +void MCMTractographyImageFilter::PrepareTractography() +{ + this->Superclass::PrepareTractography(); + + m_MCMData.resize(this->GetNumberOfWorkUnits()); + + MCMImageType *refImage = const_cast (m_MCMInterpolator->GetInputImage()); + for (unsigned int i = 0;i < this->GetNumberOfWorkUnits();++i) + m_MCMData[i] = refImage->GetDescriptionModel()->Clone(); +} + +void MCMTractographyImageFilter::SetInputImage(ModelImageType *input) +{ + this->Superclass::SetInputImage(input); + + m_MCMInterpolator = MCMInterpolatorType::New(); + + MCMImageType *castImage = dynamic_cast (input); + m_MCMInterpolator->SetInputImage(castImage); + m_MCMInterpolator->SetReferenceOutputModel(castImage->GetDescriptionModel()); +} + +bool MCMTractographyImageFilter::CheckModelCompatibility(VectorType &modelValue, itk::ThreadIdType threadId) +{ + m_MCMData[threadId]->SetModelVector(modelValue); + double weightIsotropic = 0; + + for (unsigned int i = 0;i < m_MCMData[threadId]->GetNumberOfIsotropicCompartments();++i) + { + weightIsotropic += m_MCMData[threadId]->GetCompartmentWeight(i); + + if (weightIsotropic >= m_StopIsoWeightThreshold) + return false; + } + + return true; +} + +bool MCMTractographyImageFilter::CheckIndexInImageBounds(ContinuousIndexType &index) +{ + return m_MCMInterpolator->IsInsideBuffer(index); +} + +void MCMTractographyImageFilter::GetModelValue(ContinuousIndexType &index, VectorType &modelValue) +{ + modelValue = m_MCMInterpolator->EvaluateAtContinuousIndex(index); +} + +std::vector +MCMTractographyImageFilter::GetModelPrincipalDirections(VectorType &modelValue, bool is2d, itk::ThreadIdType threadId) +{ + std::vector resDirs; + PointType resDir; + resDir.Fill(0); + + m_MCMData[threadId]->SetModelVector(modelValue); + + double sumNonIsoWeights = 0.0; + for (unsigned int i = m_MCMData[threadId]->GetNumberOfIsotropicCompartments();i < m_MCMData[threadId]->GetNumberOfCompartments();++i) + sumNonIsoWeights += m_MCMData[threadId]->GetCompartmentWeight(i); + + if (sumNonIsoWeights == 0.0) + return resDirs; + + for (unsigned int i = m_MCMData[threadId]->GetNumberOfIsotropicCompartments();i < m_MCMData[threadId]->GetNumberOfCompartments();++i) + { + if (m_MCMData[threadId]->GetCompartmentWeight(i) / sumNonIsoWeights >= m_MinimalDirectionRelativeWeight) + { + anima::BaseCompartment *workCompartment = m_MCMData[threadId]->GetCompartment(i); + anima::TransformSphericalToCartesianCoordinates(workCompartment->GetOrientationTheta(),workCompartment->GetOrientationPhi(), + 1.0,resDir); + + if (is2d) + { + resDir[2] = 0; + anima::Normalize(resDir,resDir); + } + + resDirs.push_back(resDir); + } + } + + return resDirs; +} + +MCMTractographyImageFilter::PointType +MCMTractographyImageFilter::GetNextDirection(PointType &previousDirection, VectorType &modelValue, bool is2d, + itk::ThreadIdType threadId) +{ + PointType resDir; + resDir.Fill(0); + + m_MCMData[threadId]->SetModelVector(modelValue); + + PointType tmpDir, optimalDir; + unsigned int bestIndex = m_MCMData[threadId]->GetNumberOfIsotropicCompartments(); + double maxValue = 0; + for (unsigned int i = m_MCMData[threadId]->GetNumberOfIsotropicCompartments();i < m_MCMData[threadId]->GetNumberOfCompartments();++i) + { + if (m_MCMData[threadId]->GetCompartmentWeight(i) <= 0) + continue; + + anima::BaseCompartment *workCompartment = m_MCMData[threadId]->GetCompartment(i); + anima::TransformSphericalToCartesianCoordinates(workCompartment->GetOrientationTheta(),workCompartment->GetOrientationPhi(), + 1.0,tmpDir); + + if (is2d) + { + tmpDir[2] = 0; + anima::Normalize(tmpDir,tmpDir); + } + + double dotProd = anima::ComputeScalarProduct(previousDirection,tmpDir); + + if (std::abs(dotProd) > maxValue) + { + maxValue = std::abs(dotProd); + optimalDir = tmpDir; + bestIndex = i; + } + } + + for (unsigned int i = 0;i < 3;++i) + resDir[i] = optimalDir[i]; + + if (anima::ComputeScalarProduct(previousDirection, resDir) < 0) + anima::Revert(resDir,resDir); + + double parDiffBest = m_MCMData[threadId]->GetCompartment(bestIndex)->GetApparentParallelDiffusivity(); + double perpDiffBest = m_MCMData[threadId]->GetCompartment(bestIndex)->GetApparentPerpendicularDiffusivity(); + double clBest = (parDiffBest - perpDiffBest) / (2.0 * perpDiffBest + parDiffBest); + + for (unsigned int i = 0;i < 3;++i) + resDir[i] = resDir[i] * clBest + previousDirection[i] * (1.0 - clBest); + + anima::Normalize(resDir,resDir); + if (anima::ComputeScalarProduct(previousDirection, resDir) < 0) + anima::Revert(resDir,resDir); + + return resDir; +} + +} // end of namespace anima diff --git a/Anima/diffusion/tractography/animaMCMTractographyImageFilter.h b/Anima/diffusion/tractography/animaMCMTractographyImageFilter.h new file mode 100644 index 000000000..b102633a3 --- /dev/null +++ b/Anima/diffusion/tractography/animaMCMTractographyImageFilter.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "AnimaTractographyExport.h" + +namespace anima +{ + +class ANIMATRACTOGRAPHY_EXPORT MCMTractographyImageFilter : public anima::BaseTractographyImageFilter +{ +public: + /** SmartPointer typedef support */ + typedef MCMTractographyImageFilter Self; + typedef anima::BaseTractographyImageFilter Superclass; + + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + itkNewMacro(Self) + + itkTypeMacro(MCMTractographyImageFilter,anima::BaseTractographyImageFilter) + + typedef Superclass::ModelImageType ModelImageType; + typedef anima::MCMImage MCMImageType; + typedef MCMImageType::Pointer MCMImagePointer; + typedef MCMImageType::MCMPointer MCMPointer; + + typedef Superclass::VectorType VectorType; + typedef Superclass::PointType PointType; + typedef anima::MCMLinearInterpolateImageFunction MCMInterpolatorType; + typedef MCMInterpolatorType::Pointer MCMInterpolatorPointer; + + virtual void SetInputImage(ModelImageType *input) ITK_OVERRIDE; + + void SetStopIsoWeightThreshold(double num) {m_StopIsoWeightThreshold = num;} + void SetMinimalDirectionRelativeWeight(double num) {m_MinimalDirectionRelativeWeight = num;} + +protected: + MCMTractographyImageFilter(); + virtual ~MCMTractographyImageFilter(); + + virtual void PrepareTractography() ITK_OVERRIDE; + virtual bool CheckModelCompatibility(VectorType &modelValue, itk::ThreadIdType threadId) ITK_OVERRIDE; + virtual bool CheckIndexInImageBounds(ContinuousIndexType &index) ITK_OVERRIDE; + virtual void GetModelValue(ContinuousIndexType &index, VectorType &modelValue) ITK_OVERRIDE; + virtual std::vector GetModelPrincipalDirections(VectorType &modelValue, bool is2d, itk::ThreadIdType threadId) ITK_OVERRIDE; + virtual PointType GetNextDirection(PointType &previousDirection, VectorType &modelValue, bool is2d, + itk::ThreadIdType threadId) ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(MCMTractographyImageFilter); + + double m_StopIsoWeightThreshold; + double m_MinimalDirectionRelativeWeight; + MCMInterpolatorPointer m_MCMInterpolator; + std::vector m_MCMData; +}; + +} // end of namespace anima diff --git a/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt b/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt new file mode 100644 index 000000000..6174c0db2 --- /dev/null +++ b/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt @@ -0,0 +1,46 @@ +if(BUILD_TOOLS) + +project(animaMCMProbabilisticTractography) + + +## ############################################################################# +## 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} + AnimaDataIO + AnimaPrivateTractography + AnimaTractography + AnimaMCM + AnimaOptimizers + ${TinyXML2_LIBRARY} + ${VTK_PREFIX}CommonCore + ${VTK_PREFIX}IOXML + ${VTK_PREFIX}IOLegacy + ) + + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/tractography/mcm_probabilistic_tractography/animaMCMProbabilisticTractography.cxx b/Anima/diffusion/tractography/mcm_probabilistic_tractography/animaMCMProbabilisticTractography.cxx new file mode 100644 index 000000000..f83388607 --- /dev/null +++ b/Anima/diffusion/tractography/mcm_probabilistic_tractography/animaMCMProbabilisticTractography.cxx @@ -0,0 +1,178 @@ +#include +#include +#include + +#include +#include + +#include +#include + +#include + +void ComputeKappaPolynomialCoefficients(std::vector &resVal) +{ + // Values fitted from [Zhang, 2009, Fig.3] : NEEDS TO BE RE-CHECKED + resVal[2] = 198.81; + resVal[1] = -73.214; + resVal[0] = 10.976; +} + +//Update progression of the process +void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) +{ + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< mcmArg("i","mcm","MCM image",true,"","mcm image",cmd); + TCLAP::ValueArg b0Arg("b","b0","B0 image",true,"","B0 image",cmd); + TCLAP::ValueArg noiseArg("N","noise","Noise image",true,"","noise image",cmd); + + TCLAP::ValueArg seedMaskArg("s","seed-mask","Seed mask",true,"","seed",cmd); + TCLAP::ValueArg fibersArg("o","fibers","Output fibers",true,"","fibers",cmd); + + TCLAP::ValueArg colinearityModeArg("","col-init-mode", + "Colinearity mode for initialization - 0: center, 1: outward, 2: top, 3: bottom, 4: left, 5: right, 6: front, 7: back (default: 0)", + false,0,"colinearity mode for initialization",cmd); + TCLAP::ValueArg initialDirectionModeArg("","init-mode", + "Mode for initialization - 0: take most colinear direction, 1: take highest weighted direction (default: 1)", + false,1,"mode for initialization",cmd); + + // Optional mask arguments + TCLAP::ValueArg cutMaskArg("c","cut-mask","Mask for cutting fibers (default: none)",false,"","cut mask",cmd); + TCLAP::ValueArg forbiddenMaskArg("f","forbidden-mask","Mask for removing fibers (default: none)",false,"","remove mask",cmd); + TCLAP::ValueArg filterMaskArg("","filter-mask","Mask for filtering fibers (default: none)",false,"","filter mask",cmd); + + TCLAP::ValueArg faThrArg("","fa-thr","FA threshold (default: 0.5)",false,0.5,"fa threshold",cmd); + TCLAP::ValueArg stepLengthArg("","step-length","Length of each step (default: 1)",false,1.0,"step length",cmd); + TCLAP::ValueArg nbFibersArg("","nb-fibers","Number of starting particle filters (n*n*n) per voxel (default: 1)",false,1,"number of seeds per voxel",cmd); + + TCLAP::ValueArg minLengthArg("","min-length","Minimum length for a fiber to be considered for computation (default: 10mm)",false,10.0,"minimum length",cmd); + TCLAP::ValueArg maxLengthArg("","max-length","Maximum length of a tract (default: 150mm)",false,150.0,"maximum length",cmd); + + TCLAP::ValueArg nbParticlesArg("n","nb-particles","Number of particles per filter (default: 1000)",false,1000,"number of particles",cmd); + TCLAP::ValueArg clusterMinSizeArg("","cluster-min-size","Minimal number of particles per cluster before split (default: 10)",false,10,"minimal cluster size",cmd); + + TCLAP::ValueArg resampThrArg("r","resamp-thr","Resampling relative threshold (default: 0.8)",false,0.8,"resampling threshold",cmd); + + TCLAP::ValueArg trashThrArg("","trash-thr","Relative threshold to keep fibers in trash (default: 0.1)",false,0.1,"trash threshold",cmd); + TCLAP::ValueArg kappaPriorArg("k","kappa-prior","Kappa of prior distribution (default: 15)",false,15.0,"prior kappa",cmd); + + TCLAP::ValueArg distThrArg("","dist-thr","Hausdorff distance threshold for mergine clusters (default: 0.5)",false,0.5,"merging threshold",cmd); + TCLAP::ValueArg kappaThrArg("","kappa-thr","Kappa threshold for splitting clusters (default: 30)",false,30.0,"splitting threshold",cmd); + + TCLAP::ValueArg clusterDistArg("","cluster-dist","Distance between clusters: choices are 0 (AHD), 1 (HD, default) or 2 (MHD)",false,1,"cluster distance",cmd); + + TCLAP::ValueArg freeWaterFracArg("","free-water-frac","Free water fraction threshold to stop fibers (default: 0.8)",false,0.8,"free water frac thr",cmd); + + TCLAP::SwitchArg averageClustersArg("M","average-clusters","Output only cluster mean",cmd,false); + TCLAP::SwitchArg addLocalDataArg("L","local-data","Add local data information to output tracks",cmd); + + TCLAP::ValueArg nbThreadsArg("T","nb-threads","Number of threads to run on (default: all available)",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; + } + + typedef anima::MCMProbabilisticTractographyImageFilter MainFilterType; + typedef MainFilterType::InputModelImageType InputModelImageType; + typedef MainFilterType::MaskImageType MaskImageType; + typedef MainFilterType::Vector3DType Vector3DType; + + MainFilterType::Pointer mcmTracker = MainFilterType::New(); + + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(mcmArg.getValue()); + mcmReader.Update(); + + mcmTracker->SetInputModelImage(mcmReader.GetModelVectorImage()); + + std::vector kappaCoefficients(3,0); + ComputeKappaPolynomialCoefficients(kappaCoefficients); + mcmTracker->SetKappaPolynomialCoefficients(kappaCoefficients); + + mcmTracker->SetInitialColinearityDirection((MainFilterType::ColinearityDirectionType)colinearityModeArg.getValue()); + mcmTracker->SetInitialDirectionMode((MainFilterType::InitialDirectionModeType)initialDirectionModeArg.getValue()); + + // Load seed mask + mcmTracker->SetSeedMask(anima::readImage (seedMaskArg.getValue())); + + // Load cut mask + if (cutMaskArg.getValue() != "") + mcmTracker->SetCutMask(anima::readImage (cutMaskArg.getValue())); + + // Load forbidden mask + if (forbiddenMaskArg.getValue() != "") + mcmTracker->SetForbiddenMask(anima::readImage (forbiddenMaskArg.getValue())); + + // Load filter mask + if (filterMaskArg.getValue() != "") + mcmTracker->SetFilterMask(anima::readImage (filterMaskArg.getValue())); + + typedef MainFilterType::ScalarImageType ScalarImageType; + mcmTracker->SetB0Image(anima::readImage (b0Arg.getValue())); + mcmTracker->SetNoiseImage(anima::readImage (noiseArg.getValue())); + + mcmTracker->SetNumberOfWorkUnits(nbThreadsArg.getValue()); + mcmTracker->SetNumberOfFibersPerPixel(nbFibersArg.getValue()); + mcmTracker->SetStepProgression(stepLengthArg.getValue()); + mcmTracker->SetFAThreshold(faThrArg.getValue()); + mcmTracker->SetMinLengthFiber(minLengthArg.getValue()); + mcmTracker->SetMaxLengthFiber(maxLengthArg.getValue()); + + mcmTracker->SetNumberOfParticles(nbParticlesArg.getValue()); + mcmTracker->SetMinimalNumberOfParticlesPerClass(clusterMinSizeArg.getValue()); + mcmTracker->SetResamplingThreshold(resampThrArg.getValue()); + mcmTracker->SetFiberTrashThreshold(trashThrArg.getValue()); + + mcmTracker->SetIsotropicThreshold(freeWaterFracArg.getValue()); + mcmTracker->SetKappaOfPriorDistribution(kappaPriorArg.getValue()); + + mcmTracker->SetPositionDistanceFuseThreshold(distThrArg.getValue()); + mcmTracker->SetKappaSplitThreshold(kappaThrArg.getValue()); + mcmTracker->SetClusterDistance(clusterDistArg.getValue()); + + bool computeLocalColors = (fibersArg.getValue().find(".fds") != std::string::npos) && (addLocalDataArg.isSet()); + mcmTracker->SetComputeLocalColors(computeLocalColors); + mcmTracker->SetMAPMergeFibers(averageClustersArg.isSet()); + + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + mcmTracker->AddObserver(itk::ProgressEvent(), callback); + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + try + { + mcmTracker->Update(); + mcmTracker->RemoveAllObservers(); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + tmpTime.Stop(); + std::cout << "Tracking time: " << tmpTime.GetTotal() << "s" << std::endl; + + anima::ShapesWriter writer; + writer.SetInputData(mcmTracker->GetOutput()); + writer.SetFileName(fibersArg.getValue()); + writer.Update(); + + return EXIT_SUCCESS; +} diff --git a/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt b/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt new file mode 100644 index 000000000..2aacd5145 --- /dev/null +++ b/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt @@ -0,0 +1,44 @@ +if(BUILD_TOOLS) + +project(animaMCMTractography) + + +## ############################################################################# +## 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} + AnimaDataIO + AnimaPrivateTractography + AnimaTractography + AnimaMCMPrivate + AnimaMCM + AnimaOptimizers + ${VTK_PREFIX}CommonCore + ${VTKSYS_LIBRARY} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/diffusion/tractography/mcm_tractography/animaMCMTractography.cxx b/Anima/diffusion/tractography/mcm_tractography/animaMCMTractography.cxx new file mode 100644 index 000000000..0f7695e50 --- /dev/null +++ b/Anima/diffusion/tractography/mcm_tractography/animaMCMTractography.cxx @@ -0,0 +1,109 @@ +#include +#include + +#include +#include +#include + +#include + +#include + +//Update progression of the process +void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) +{ + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< mcmArg("i","input-mcm","Input MCM image",true,"","MCM image",cmd); + TCLAP::ValueArg seedMaskArg("s","seed-mask","Seed mask",true,"","seed",cmd); + TCLAP::ValueArg fibersArg("o","fibers","Output fibers",true,"","fibers",cmd); + + TCLAP::ValueArg filterMaskArg("","filter-mask","Mask for filtering fibers (default: none)",false,"","filter mask",cmd); + TCLAP::ValueArg cutMaskArg("c","cut-mask","Mask for cutting fibers (default: none)",false,"","cut mask",cmd); + TCLAP::ValueArg forbiddenMaskArg("f","forbidden-mask","Mask for removing fibers (default: none)",false,"","remove mask",cmd); + + TCLAP::ValueArg isoThrArg("I","iso-thr","Isotropic weight threshold (default: 0.8)",false,0.8,"Isotropic weight threshold",cmd); + TCLAP::ValueArg relWeightArg("R","start-rel-weight","Minimal relative weight of direction to start a fiber (default: 0.2)",false,0.2,"Minimal relative weight of start direction",cmd); + + TCLAP::ValueArg stopAngleArg("a","angle-max","Maximum angle for tracking (default: 60)",false,60.0,"maximum track angle",cmd); + TCLAP::ValueArg stepLengthArg("","step-length","Length of each step (in mm, default: 0.5)",false,0.5,"step length",cmd); + TCLAP::ValueArg nbFibersArg("","nb-fibers","Number of starting filters (n*n*n) per voxel (default: 1)",false,1,"number of seeds per voxel",cmd); + + TCLAP::ValueArg minLengthArg("","min-length","Minimum length for a fiber to be considered for computation (default: 10mm)",false,10.0,"minimum length",cmd); + TCLAP::ValueArg maxLengthArg("","max-length","Maximum length of a tract (default: 200mm)",false,200.0,"maximum length",cmd); + + TCLAP::SwitchArg addLocalDataArg("L","local-data","Add local data information to output tracks",cmd); + + TCLAP::ValueArg nbThreadsArg("T","nb-threads","Number of threads to run on (default: all available)",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; + } + + typedef anima::MCMTractographyImageFilter MainFilterType; + typedef MainFilterType::MaskImageType MaskImageType; + typedef itk::ImageFileReader MaskReaderType; + + MainFilterType::Pointer mcmTracker = MainFilterType::New(); + + anima::MCMFileReader mcmReader; + mcmReader.SetFileName(mcmArg.getValue()); + mcmReader.Update(); + + mcmTracker->SetInputImage(mcmReader.GetModelVectorImage()); + + mcmTracker->SetSeedingMask(anima::readImage (seedMaskArg.getValue())); + + if (filterMaskArg.getValue() != "") + mcmTracker->SetFilteringMask(anima::readImage (filterMaskArg.getValue())); + + if (cutMaskArg.getValue() != "") + mcmTracker->SetCutMask(anima::readImage (cutMaskArg.getValue())); + + if (forbiddenMaskArg.getValue() != "") + mcmTracker->SetForbiddenMask(anima::readImage (forbiddenMaskArg.getValue())); + + mcmTracker->SetNumberOfWorkUnits(nbThreadsArg.getValue()); + mcmTracker->SetNumberOfFibersPerPixel(nbFibersArg.getValue()); + mcmTracker->SetStepProgression(stepLengthArg.getValue()); + mcmTracker->SetStopIsoWeightThreshold(isoThrArg.getValue()); + mcmTracker->SetMinimalDirectionRelativeWeight(relWeightArg.getValue()); + mcmTracker->SetMaxFiberAngle(stopAngleArg.getValue()); + mcmTracker->SetMinLengthFiber(minLengthArg.getValue()); + mcmTracker->SetMaxLengthFiber(maxLengthArg.getValue()); + + bool computeLocalColors = (fibersArg.getValue().find(".fds") != std::string::npos) && (addLocalDataArg.isSet()); + mcmTracker->SetComputeLocalColors(computeLocalColors); + + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + mcmTracker->AddObserver(itk::ProgressEvent(), callback); + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + mcmTracker->Update(); + mcmTracker->RemoveAllObservers(); + + tmpTime.Stop(); + std::cout << "Tracking time: " << tmpTime.GetTotal() << "s" << std::endl; + + anima::ShapesWriter writer; + writer.SetInputData(mcmTracker->GetOutput()); + writer.SetFileName(fibersArg.getValue()); + writer.Update(); + + return EXIT_SUCCESS; +} From 8e14d519b9629af1c414298f214810dd554294bd Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Fri, 7 Apr 2023 17:14:09 +0200 Subject: [PATCH 02/17] done fusion for multi_compartment_base --- .../animaMCMDDIWeightedAverager.cxx | 86 +++++++++++++++++++ .../animaMCMDDIWeightedAverager.h | 48 +++++++++++ .../animaMCMFileReader.h | 2 +- .../animaMCMFileReader.hxx | 2 + 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx create mode 100644 Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h diff --git a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx new file mode 100644 index 000000000..0eabab11d --- /dev/null +++ b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx @@ -0,0 +1,86 @@ +#include +#include + +namespace anima +{ + +MCMDDIWeightedAverager::MCMDDIWeightedAverager() +{ + m_DDIInterpolationMethod = 3; +} + +void MCMDDIWeightedAverager::SetDDIInterpolationMethod(unsigned int method) +{ + if (m_DDIInterpolationMethod == method) + return; + + m_DDIInterpolationMethod = method; + this->SetUpToDate(false); +} + +void MCMDDIWeightedAverager::ComputeNonTensorDistanceMatrix() +{ + if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) + itkExceptionMacro("Only DDI supported in addition to tensor") + + unsigned int numCompartments = m_WorkCompartmentsVector.size(); + m_InternalDDIKappa.resize(numCompartments); + m_InternalDDINu.resize(numCompartments); + m_InternalDDIDiffusivity.resize(numCompartments); + m_InternalDDIDirections.resize(numCompartments); + vnl_vector tmpVec(3); + tmpVec.fill(0); + for (unsigned int i = 0;i < numCompartments;++i) + m_InternalDDIDirections[i] = tmpVec; + + for (unsigned int i = 0;i < numCompartments;++i) + { + anima::TransformSphericalToCartesianCoordinates(m_WorkCompartmentsVector[i]->GetOrientationTheta(), + m_WorkCompartmentsVector[i]->GetOrientationPhi(),1.0, + m_InternalDDIDirections[i]); + + m_InternalDDIKappa[i] = m_WorkCompartmentsVector[i]->GetOrientationConcentration(); + m_InternalDDIDiffusivity[i] = m_WorkCompartmentsVector[i]->GetAxialDiffusivity(); + m_InternalDDINu[i] = m_WorkCompartmentsVector[i]->GetExtraAxonalFraction(); + } + + anima::ComputeDistanceMatrixBetweenFascicles(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, + m_DDIInterpolationMethod,m_InternalDistanceMatrix); +} + +void MCMDDIWeightedAverager::ComputeOutputNonTensorModel() +{ + if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) + itkExceptionMacro("Only DDI supported in addition to tensor") + + std::vector referenceDDIWeights = m_WorkCompartmentWeights; + + double averageNu = 0,averageDiffusivity = 0,averageKappa = 0,averageWeight = 0; + vnl_vector averageDirection(3); + + unsigned int nbOfUsefulFascicles = m_WorkCompartmentWeights.size(); + unsigned int numIsoCompartments = this->GetUntouchedOutputModel()->GetNumberOfIsotropicCompartments(); + unsigned int numberOfOutputCompartments = m_InternalSpectralMemberships[0].size(); + + for (unsigned int i = 0;i < numberOfOutputCompartments;++i) + { + for (unsigned int j = 0;j < nbOfUsefulFascicles;++j) + m_WorkCompartmentWeights[j] = referenceDDIWeights[j] * m_InternalSpectralMemberships[j][i]; + + anima::DDIAveraging(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, + m_WorkCompartmentWeights,m_DDIInterpolationMethod,averageNu,averageDiffusivity,averageKappa, + averageDirection,averageWeight); + + anima::BaseCompartment *workCompartment = this->GetUntouchedOutputModel()->GetCompartment(i+numIsoCompartments); + workCompartment->SetAxialDiffusivity(averageDiffusivity); + workCompartment->SetOrientationConcentration(averageKappa); + workCompartment->SetExtraAxonalFraction(averageNu); + anima::TransformCartesianToSphericalCoordinates(averageDirection,averageDirection); + workCompartment->SetOrientationTheta(averageDirection[0]); + workCompartment->SetOrientationPhi(averageDirection[1]); + + m_InternalOutputWeights[i+numIsoCompartments] = averageWeight; + } +} + +} // end namespace anima diff --git a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h new file mode 100644 index 000000000..5d7e5b167 --- /dev/null +++ b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include + +#include + +#include + +namespace anima +{ + +/** + * @brief Computes a weighted average of input multi-compartment models including DDI. The output model is at the same + * time giving the number and type of output compartments but also its parameters are erased when performing Update + * to get the result + */ +class ANIMAMCMBASE_EXPORT MCMDDIWeightedAverager : public anima::MCMWeightedAverager +{ +public: + typedef MCMDDIWeightedAverager Self; + typedef anima::MCMWeightedAverager Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + /** Run-time type information (and related methods) */ + itkTypeMacro(MCMDDIWeightedAverager, anima::MCMWeightedAverager) + + itkNewMacro(Self) + + void SetDDIInterpolationMethod(unsigned int method); + +protected: + MCMDDIWeightedAverager(); + ~MCMDDIWeightedAverager() {} + + void ComputeNonTensorDistanceMatrix() ITK_OVERRIDE; + void ComputeOutputNonTensorModel() ITK_OVERRIDE; + +private: + unsigned int m_DDIInterpolationMethod; + + // Internal work variables + std::vector m_InternalDDIKappa, m_InternalDDINu, m_InternalDDIDiffusivity; + std::vector < vnl_vector > m_InternalDDIDirections; +}; + +} // end namespace anima diff --git a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.h b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.h index 3b26f31fb..5a5743769 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.h +++ b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.h @@ -33,7 +33,7 @@ class MCMFileReader void SetFileName(std::string fileName) {m_FileName = fileName;} void Update(); - virtual anima::BaseCompartment::Pointer CreateCompartmentForType(std::string &compartmentType); + anima::BaseCompartment::Pointer CreateCompartmentForType(std::string &compartmentType); private: OutputImagePointer m_OutputImage; diff --git a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx index 3f156a9be..e09238adb 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx +++ b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx @@ -191,6 +191,8 @@ MCMFileReader additionalCompartment = anima::TensorCompartment::New(); else if (compartmentType == "NODDI") additionalCompartment = anima::NODDICompartment::New(); + else if (compartmentType == "DDI") + additionalCompartment = anima::DDICompartment::New(); else { std::string error("Unsupported compartment type: "); From d4b6700146641e4ab2707172658c31a595dcb4da Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 10:51:38 +0200 Subject: [PATCH 03/17] done minimal fusion for math-tools to try interpolate compilation. --- Anima/diffusion/tractography/CMakeLists.txt | 2 +- .../interpolator/animaDDIAveragingTools.h | 50 ++ .../interpolator/animaDDIAveragingTools.hxx | 643 ++++++++++++++++++ .../animaMCMLinearInterpolateImageFunction.h | 10 +- ...animaMCMLinearInterpolateImageFunction.hxx | 20 +- .../animaMCMDDIWeightedAverager.cxx | 86 --- .../animaMCMDDIWeightedAverager.h | 48 -- .../animaMCMWeightedAverager.cxx | 71 +- .../animaMCMWeightedAverager.h | 17 +- .../statistical_distributions/CMakeLists.txt | 6 + .../animaDDIDistribution.h | 32 + .../animaDDIDistribution.hxx | 274 ++++++++ ...maSampleImageFromDistributionImageFilter.h | 56 ++ ...SampleImageFromDistributionImageFilter.hxx | 119 ++++ .../CMakeLists.txt | 36 + .../animaSampleImageFromDistribution.cxx | 58 ++ 16 files changed, 1375 insertions(+), 153 deletions(-) create mode 100644 Anima/math-tools/interpolator/animaDDIAveragingTools.h create mode 100644 Anima/math-tools/interpolator/animaDDIAveragingTools.hxx delete mode 100644 Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx delete mode 100644 Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h create mode 100644 Anima/math-tools/statistical_distributions/animaDDIDistribution.h create mode 100644 Anima/math-tools/statistical_distributions/animaDDIDistribution.hxx create mode 100644 Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h create mode 100644 Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.hxx create mode 100644 Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt create mode 100644 Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx diff --git a/Anima/diffusion/tractography/CMakeLists.txt b/Anima/diffusion/tractography/CMakeLists.txt index a57df0825..c094864db 100644 --- a/Anima/diffusion/tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/CMakeLists.txt @@ -56,6 +56,6 @@ add_subdirectory(fibers_counter) add_subdirectory(fibers_filterer) add_subdirectory(odf_probabilistic_tractography) add_subdirectory(mcm_probabilistic_tractography) -add_subdirectory(mcm__tractography) +add_subdirectory(mcm_tractography) endif() #USE_VTK AND VTK_FOUND diff --git a/Anima/math-tools/interpolator/animaDDIAveragingTools.h b/Anima/math-tools/interpolator/animaDDIAveragingTools.h new file mode 100644 index 000000000..1815b20bd --- /dev/null +++ b/Anima/math-tools/interpolator/animaDDIAveragingTools.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace anima +{ + +template void ComputeVoxelWeights(const typename itk::VectorImage::IndexType &inputIndex, + std::vector::IndexType> &variableIndex, + std::vector &wPosition, const typename itk::Image::SizeType &bound, + const int step, unsigned int nbVoxels); + +template void RemoveNullFascicles(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + std::vector& w, + std::vector& wFree, + const bool optionsBoundV); + +template void CreateCovarianceMatrixFromDDIParameter(const std::vector& v, const std::vector& d, + const std::vector& kappa, const std::vector >& mu, + std::vector >& Sigma); + +template void DDIAveraging(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + std::vector& w, + const int method, double &averageNu, double &averageDiffusivity, + double &averageKappa, vnl_vector &averageDirection, + double &averageWeight); + +template void FreeWaterDDIAveraging(std::vector < std::vector > &wFree, + std::vector < std::vector > &dFree, + std::vector &averageDiffusivity, + std::vector &averageFreeWeight); + +template +void ComputeDistanceMatrixBetweenFascicles(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + int method, vnl_matrix& distanceMatrix); + +} // end of namespace anima + +#include "animaDDIAveragingTools.hxx" diff --git a/Anima/math-tools/interpolator/animaDDIAveragingTools.hxx b/Anima/math-tools/interpolator/animaDDIAveragingTools.hxx new file mode 100644 index 000000000..ef42a1232 --- /dev/null +++ b/Anima/math-tools/interpolator/animaDDIAveragingTools.hxx @@ -0,0 +1,643 @@ +#pragma once +#include "animaDDIAveragingTools.h" + +#include +#include +#include + +#include + +namespace anima +{ + +template void ComputeVoxelWeights(const typename itk::VectorImage::IndexType &inputIndex, + std::vector::IndexType> &variableIndex, + std::vector &wPosition, const typename itk::Image::SizeType &bound, + const int step, unsigned int nbVoxels) +{ + int boundx = bound[0]; + int boundy = bound[1]; + int boundz = bound[2]; + + int xIndex = inputIndex[0]; + int yIndex = inputIndex[1]; + int zIndex = inputIndex[2]; + + int modx = xIndex % step; + int mody = yIndex % step; + int modz = zIndex % step; + + double scale = 1/double(step); + + typename itk::Image::IndexType tempIndex; + + variableIndex.clear(); + wPosition.clear(); + + //Enumerate all the cases + if (xIndex < boundx - step + 1 && yIndex < boundy - step + 1 && zIndex < boundz - step + 1) + { + // Voxel on the grid + if (modx == 0 && mody == 0 && modz == 0) + nbVoxels = 0; + + // Two voxels + else if (modx != 0 && mody == 0 && modz == 0) + { + nbVoxels = 2; + tempIndex[1] = yIndex; + tempIndex[2] = zIndex; + + for (int varx = - modx; varx < step; varx += step) + { + tempIndex[0] = xIndex + varx; + variableIndex.push_back(tempIndex); + wPosition.push_back(1 - scale*std::abs(varx)); + } + } + else if (modx == 0 && mody != 0 && modz == 0) + { + nbVoxels = 2; + tempIndex[0] = xIndex; + tempIndex[2] = zIndex; + for (int vary = - mody; vary < step; vary += step) + { + tempIndex[1] = yIndex + vary; + variableIndex.push_back(tempIndex); + wPosition.push_back(1 - scale*std::abs(vary)); + } + } + else if (modx == 0 && mody == 0 && modz != 0) + { + nbVoxels = 2; + tempIndex[0] = xIndex; + tempIndex[1] = yIndex; + for (int varz = - modz; varz < step; varz += step) + { + tempIndex[2] = zIndex + varz; + variableIndex.push_back(tempIndex); + wPosition.push_back(1 - scale*std::abs(varz)); + } + } + + // Four voxels + else if (modx != 0 && mody != 0 && modz == 0) + { + nbVoxels = 4; + tempIndex[2] = zIndex; + for (int varx = -modx; varx < step; varx += step) + { + for (int vary = -mody; vary < step; vary += step) + { + tempIndex[0] = xIndex + varx; + tempIndex[1] = yIndex + vary; + variableIndex.push_back(tempIndex); + wPosition.push_back((1 - scale*std::abs(varx))*(1 - scale*std::abs(vary))); + } + } + } + else if (modx != 0 && mody == 0 && modz != 0) + { + nbVoxels = 4; + tempIndex[1] = yIndex; + for (int varx = -modx; varx < step; varx += step) + { + for (int varz = -modz; varz < step; varz += step) + { + tempIndex[0] = xIndex + varx; + tempIndex[2] = zIndex + varz; + variableIndex.push_back(tempIndex); + wPosition.push_back((1 - scale*std::abs(varx))*(1 - scale*std::abs(varz))); + } + } + } + else if (modx == 0 && mody != 0 && modz != 0) + { + nbVoxels = 4; + tempIndex[0] = xIndex; + for (int vary = -mody; vary < step; vary += step) + { + for (int varz = -modz; varz < step; varz += step) + { + tempIndex[1] = yIndex + vary; + tempIndex[2] = zIndex + varz; + variableIndex.push_back(tempIndex); + wPosition.push_back((1 - scale*std::abs(vary))*(1 - scale*std::abs(varz))); + } + } + } + + //Eight voxels + else if (modx != 0 && mody != 0 && modz != 0) + { + nbVoxels = 8; + for (int varx = -modx; varx < step; varx += step) + { + for (int vary = -mody; vary < step; vary += step) + { + for (int varz = -modz; varz < step; varz += step) + { + tempIndex[0] = xIndex + modx; + tempIndex[1] = yIndex + mody; + tempIndex[2] = zIndex + modz; + variableIndex.push_back(tempIndex); + wPosition.push_back((1 - scale*std::abs(varx))*(1 - scale*std::abs(vary))*(1 - scale*std::abs(varz))); + } + } + } + } + } + else + nbVoxels = 0; + +} + +template void RemoveNullFascicles(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + std::vector& w, + std::vector& wFree, + const bool optionsBoundV) +{ + int nbFascicles = v.size(); + int nbFreeWater = wFree.size(); + + double sumTotal = 0; + + for (int i = 0;i < nbFascicles;++i) + { + //If the weight is null remove the fascicule + if (w[i] == 0) + { + v.erase(v.begin() + i); + d.erase(d.begin() + i); + kappa.erase(kappa.begin() + i); + directions.erase(directions.begin() + i); + w.erase(w.begin() + i); + + nbFascicles--; + i--; + } + else + sumTotal += w[i]; + } + + // Add free water area to the sumTotal + for (int i = 0;i < nbFreeWater;++i) + sumTotal += wFree[i]; + + //Correct weights of fascicles + for (int i = 0;i < nbFascicles;++i) + { + w[i] /= sumTotal; + // Set boundaries of v between 0.01 and 0.99 + if (optionsBoundV) + { + if (v[i] == 1) + v[i] = 0.99; + if (v[i] == 0) + v[i] = 0.01; + } + } + + //Correct weights of free water too. + for (int i = 0; i < nbFreeWater; ++i) + wFree[i] /= sumTotal; +} + +template void CreateCovarianceMatrixFromDDIParameter(const std::vector& v, const std::vector& d, + const std::vector& kappa, const std::vector >& mu, + std::vector >& Sigma) + +{ + int nbFascicles = v.size(); + + vnl_matrix U(3,3); + vnl_matrix Id(3,3); + Id.set_identity(); + vnl_matrix muMatrix(3,1); + for (int i = 0; i < nbFascicles; ++i) + { + // Copy vector mu into a matrix (3,1) to do the product + for (int j = 0; j < 3; j ++) + muMatrix(j,0) = mu[i](j); + + U = muMatrix*muMatrix.transpose(); + Sigma[i] = (1 - v[i])*(d[i]/(1 + kappa[i])) * (Id + kappa[i]*U); + + } +} + +template void DDIAveraging(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + std::vector& w, + const int method, double &averageNu, double &averageDiffusivity, + double &averageKappa, vnl_vector &averageDirection, + double &averageWeight) +{ + // Initialize some standard value + ScalarType step = 1; + ScalarType epsilon = 0.0001; + + // First of all test if there is some null input value and remove it. + int nbFascicles = v.size(); + + averageNu = 0; + averageDiffusivity = 0; + averageKappa = 0; + averageDirection.fill(0); + averageWeight = 0; + + if (nbFascicles == 0) + return; + + if (nbFascicles == 1) + { + averageNu = v[0]; + averageDiffusivity = d[0]; + averageKappa = kappa[0]; + averageDirection = directions[0]; + averageWeight = w[0]; + + return; + } + + // Start by putting all directions into the same half sphere from direction cosine matrix + vnl_matrix dirsMatrix(3,3,0); + vnl_matrix tmpMat(3,3); + double totalWeights = 0; + + anima::LogEuclideanTensorCalculator ::Pointer leCalculator = anima::LogEuclideanTensorCalculator ::New(); + + for (unsigned int i = 0;i < nbFascicles;++i) + { + tmpMat.set_identity(); + tmpMat *= epsilon; + + for (unsigned int j = 0;j < 3;++j) + for (unsigned int k = j;k < 3;++k) + { + double tmpVal = directions[i][j] * directions[i][k]; + tmpMat(j,k) += tmpVal; + if (j != k) + tmpMat(k,j) += tmpVal; + } + + leCalculator->GetTensorLogarithm(tmpMat,tmpMat); + dirsMatrix += w[i] * tmpMat; + totalWeights += w[i]; + } + + dirsMatrix /= totalWeights; + leCalculator->GetTensorExponential(dirsMatrix,dirsMatrix); + + vnl_matrix eigVecs(3,3); + vnl_diag_matrix eigVals(3); + + itk::SymmetricEigenAnalysis < vnl_matrix , vnl_diag_matrix > eigSystem(3); + eigSystem.SetOrderEigenValues(true); + eigSystem.ComputeEigenValuesAndVectors(dirsMatrix,eigVals,eigVecs); + + //Put all directions in the same half sphere + for (unsigned int i = 0; i < nbFascicles; ++i) + { + double scalarProduct = directions[i][2]; + + if (scalarProduct < 0) + directions[i] *= -1; + + directions[i] /= directions[i].two_norm(); + } + + // Then create the covariance matrix from parameters + std::vector > Sigma (nbFascicles); + CreateCovarianceMatrixFromDDIParameter(v, d, kappa, directions, Sigma); + + // Compute + std::vector radius; + for (int i = 0; i< nbFascicles; i++) + radius.push_back(std::sqrt(v[i]*d[i])); + + // Then normalize the sum of weight to one + for (unsigned int i = 0;i < nbFascicles;++i) + w[i] /= totalWeights; + + // Estimate directly radius + ScalarType mRadius2 = 0; + for (unsigned int i = 0;i < nbFascicles;++i) + mRadius2 += w[i]*radius[i]*radius[i]; + + //Estimate directly Sigma with the log-euclidean Frechet mean + vnl_matrix lSigma (3,3,0), mSigma(3,3); + + for (unsigned int i = 0;i < nbFascicles;++i) + { + leCalculator->GetTensorLogarithm(Sigma[i],tmpMat); + lSigma += w[i]*tmpMat; + } + + leCalculator->GetTensorExponential(lSigma,mSigma); + + //Get eigenvalue and eigen vector of mSigma + vnl_matrix sigmaEigVecs(3,3); + vnl_diag_matrix sigmaEigVals(3); + eigSystem.ComputeEigenValuesAndVectors(mSigma,sigmaEigVals,sigmaEigVecs); + + ScalarType L1 = sigmaEigVals(2,2); + ScalarType L2 = sigmaEigVals(1,1); + ScalarType L3 = sigmaEigVals(0,0); + ScalarType Lperp = std::sqrt(L2*L3); + vnl_vector muSigma = sigmaEigVecs.get_row(2); + + vnl_vector mU(3,0); + ScalarType mV1 = 0, mV2 = 0, mV = 0, mD = 0, mKappa = 0; + + //Estimate mu, kappa and nu following different methods + switch (method) + { + //Simplest averaging (Classic) + case 0: + { + std::vector > originalAngles(nbFascicles); + vnl_vector averageAngle(3, 0); + averageAngle[0] = 1; + for (int i = 0; i < nbFascicles; i++) + { + anima::TransformCartesianToSphericalCoordinates(directions[i], originalAngles[i]); + averageAngle[1] += w[i]*originalAngles[i][1]; + averageAngle[2] += w[i]*originalAngles[i][2]; + + mV += w[i]*v[i]; + mD += w[i]*d[i]; + mKappa += w[i]*kappa[i]; + } + //Come back in cartesian coordinate + anima::TransformSphericalToCartesianCoordinates(averageAngle, mU); + break; + } + + //Tensor averaging + case 1: + { + mU = eigVecs.get_row(2); + for (int i = 0; i < nbFascicles; i++) + { + mV += w[i]*v[i]; + mD += w[i]*d[i]; + mKappa += w[i]*kappa[i]; + } + break; + } + + //log VMF + case 2: + { + double maxWeight = 0; + double maxIndex = 0; + for (unsigned int i = 0;i < nbFascicles;++i) + { + if (w[i] > maxWeight) + { + maxWeight = w[i]; + maxIndex = i; + } + } + mKappa = kappa[maxIndex]; + ScalarType ConditionStop = 0.001; + ScalarType lKappa = ConditionStop + 1; + while (std::abs(lKappa) > ConditionStop ) + { + lKappa = 0; + //Average in the tangent space + for (unsigned int i = 0; i < nbFascicles; ++i) + lKappa = lKappa + (step/nbFascicles)*w[i]*log(kappa[i] / mKappa); + + mKappa = mKappa*exp(lKappa); + } + mU = eigVecs.get_row(2); + mV1 = mRadius2/(L1 + mRadius2); + mV2 = mRadius2/(L2 * (mKappa + 1) + mRadius2); + mV = (mV1 + mV2)/2; + mD = mRadius2/mV; + + break; + } + + //covariance Analytic + case 3: + { + mU = muSigma; + mV = mRadius2/(L1+mRadius2); + mKappa = (L1 - Lperp)/Lperp; + mD = mRadius2/mV; + + break; + } + } + + //Record results in the DDI format + for (unsigned int i = 0;i < 3;++i) + averageDirection[i] = mU[i]; + averageKappa = mKappa; + averageDiffusivity = mD; + averageNu = mV; + averageWeight = totalWeights; +} + +template +void +FreeWaterDDIAveraging(std::vector < std::vector > &wFree, + std::vector < std::vector > &dFree, + std::vector &averageDiffusivities, + std::vector &averageFreeWeights) + +{ + unsigned int numIsoCompartments = wFree.size(); + unsigned int nbFascicles = 0; + + if (numIsoCompartments > 0) + nbFascicles = wFree[0].size(); + + averageDiffusivities.resize(numIsoCompartments); + std::fill(averageDiffusivities.begin(),averageDiffusivities.end(),0.0); + averageFreeWeights.resize(numIsoCompartments); + std::fill(averageFreeWeights.begin(),averageFreeWeights.end(),0.0); + + for (unsigned int j = 0;j < numIsoCompartments;++j) + { + for (unsigned int i = 0; i < nbFascicles; ++i) + { + if ((wFree[j][i] != 0)&&(dFree[j][i] != 0)) + { + averageFreeWeights[j] += wFree[j][i]; + averageDiffusivities[j] += wFree[j][i] * std::log(dFree[j][i]); + } + } + } + + for (unsigned int j = 0;j < numIsoCompartments;++j) + { + if (averageFreeWeights[j] != 0) + averageDiffusivities[j] = std::exp(averageDiffusivities[j] / averageFreeWeights[j]); + } +} + +template< class ScalarType> +void ComputeDistanceMatrixBetweenFascicles(std::vector& v, std::vector& d, + std::vector& kappa, + std::vector >& directions, + int method, vnl_matrix& distanceMatrix) + +{ + int kappaW = 1; + int radiusW = 1; + double epsilon = 0.0001; + int nbFascicles = v.size(); + + distanceMatrix.set_size(nbFascicles,nbFascicles); + distanceMatrix.fill(0.0); + + std::vector workV(2); + std::vector workD(2); + std::vector workKappa(2); + std::vector > workU(2); + std::vector > workUVec(2); + + for (int i = 0; i < 2; ++i) + { + for (int k=0; k < 3; ++k) + { + workUVec[i].set_size(3); + workU[i].set_size(3,1); + } + } + std::vector < vnl_matrix > workSigma(2); + + vnl_matrix kappaMatrix(nbFascicles, nbFascicles, 0); + vnl_matrix radiusMatrix(nbFascicles, nbFascicles, 0); + vnl_matrix uMatrix(nbFascicles, nbFascicles, 0); + + double uSum = 0, kappaSum = 0, rayonSum = 0; + distanceMatrix.fill(0); + + anima::LogEuclideanTensorCalculator ::Pointer leCalculator = anima::LogEuclideanTensorCalculator ::New(); + + for (int i = 0; i < nbFascicles; ++i) + { + for (int j = i + 1; j < nbFascicles; ++j) + { + for (int k = 0; k < 3; ++k) + { + workU[0][k][0] = directions[i][k]; + workU[1][k][0] = directions[j][k]; + workUVec[0][k] = directions[i][k]; + workUVec[1][k] = directions[j][k]; + } + + workKappa[0] = kappa[i]; + workKappa[1] = kappa[j]; + + workD[0] = d[i]; + workD[1] = d[j]; + + workV[0] = v[i]; + workV[1] = v[j]; + + // Classic + if (method == 0) + { + std::vector > angles(2); + TransformCartesianToSphericalCoordinates(workUVec[0], angles[0]); + TransformCartesianToSphericalCoordinates(workUVec[1], angles[1]); + + uMatrix[i][j] = std::sqrt(std::pow(angles[0][1] - angles[1][1], 2) + std::pow(angles[0][2] - angles[1][2], 2)); + radiusMatrix[i][j] = std::abs(workV[0]*workD[0] - workV[1]*workD[1]); + kappaMatrix[i][j] = std::abs(workKappa[0] - workKappa[1]); + + uSum += uMatrix[i][j]; + kappaSum += kappaMatrix[i][j]; + rayonSum += radiusMatrix[i][j]; + + } + else if ((method == 1) || (method == 2)) // Tensor and log VMF + { + vnl_matrix epsMatrix(3,3); + epsMatrix.set_identity(); + epsMatrix *= epsilon; + vnl_matrix u0Tensor = epsMatrix + workU[0]*workU[0].transpose(); + vnl_matrix u1Tensor = epsMatrix + workU[1]*workU[1].transpose(); + + vnl_matrix logu0(3,3); + vnl_matrix logu1(3,3); + vnl_matrix Diff(3,3,0); + + leCalculator->GetTensorLogarithm(u0Tensor,logu0); + leCalculator->GetTensorLogarithm(u1Tensor,logu1); + Diff = logu0 - logu1; + uMatrix[i][j] = Diff.frobenius_norm(); + radiusMatrix[i][j] = std::abs(workV[0]*workD[0] - workV[1]*workD[1]); + if (method == 2) + kappaMatrix[i][j] = std::abs(std::log(workKappa[0]) - std::log(workKappa[1])); + else + kappaMatrix[i][j] = std::abs(workKappa[0] - workKappa[1]); + + uSum += uMatrix[i][j]; + kappaSum += kappaMatrix[i][j]; + rayonSum += radiusMatrix[i][j]; + } + else if (method == 3) // covariance Analytic + { + CreateCovarianceMatrixFromDDIParameter(workV, workD, workKappa, workUVec, workSigma); + vnl_matrix logSigma0(3,3,0); + vnl_matrix logSigma1(3,3,0); + vnl_matrix Diff(3,3,0); + + leCalculator->GetTensorLogarithm(workSigma[0],logSigma0); + leCalculator->GetTensorLogarithm(workSigma[1],logSigma1); + Diff = logSigma0 - logSigma1; + + double distVal = Diff.frobenius_norm(); + distanceMatrix(i,j) = distVal * distVal; + } + else + std::cerr << "Method needs to be included between 0 and 3" << std::endl; + } + } + + // Compute distance matrix for all cases exept analytic + if (method != 3) + { + for (int i = 0; i < nbFascicles; ++i) + { + for (int j = i + 1; j < nbFascicles; ++j) + { + if (uSum != 0) + uMatrix[i][j] = uMatrix[i][j]/uSum; + else + uMatrix[i][j] = 0; + + if (kappaSum != 0) + kappaMatrix[i][j] = kappaW*kappaMatrix[i][j]/kappaSum; + else + kappaMatrix[i][j] = 0; + + if (rayonSum != 0) + radiusMatrix[i][j] = radiusW*radiusMatrix[i][j]/rayonSum; + else + radiusMatrix[i][j] = 0; + + double distVal = uMatrix[i][j] + kappaMatrix[i][j] + radiusMatrix[i][j]; + distanceMatrix(i,j) = distVal * distVal; + } + } + } + + for (int i = 0; i < nbFascicles; ++i) + { + for (int j = i + 1; j < nbFascicles; ++j) + distanceMatrix(j,i) = distanceMatrix(i,j); + } +} + +} // end of namespace anima diff --git a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h index dee98c64b..e3c793e86 100644 --- a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h +++ b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h @@ -98,13 +98,13 @@ public itk::InterpolateImageFunction void TestModelsAdequation(MCModelPointer &inputModel, MCModelPointer &outputModel); //! Resets averager pointers, can be overloaded to handle more models - virtual void ResetAveragePointers(MCModelPointer &model); + void ResetAveragePointers(MCModelPointer &model); //! Check if model can actually be interpolated - virtual bool CheckModelCompatibility(MCModelPointer &model); + bool CheckModelCompatibility(MCModelPointer &model); - //! Sets averager specific parameters if sub-classes are derived - virtual void SetSpecificAveragerParameters(unsigned int threadIndex) const {} + //! Sets averager specific parameters + void SetSpecificAveragerParameters(unsigned int threadIndex) const; unsigned int GetFreeWorkIndex() const; void UnlockWorkIndex(unsigned int index) const; @@ -117,6 +117,8 @@ public itk::InterpolateImageFunction static const unsigned long m_Neighbors; static const unsigned int m_SphereDimension = 3; + unsigned int m_DDIInterpolationMethod; + mutable std::mutex m_LockUsedModels; mutable std::vector m_UsedModels; diff --git a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.hxx b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.hxx index a51d5aede..f6ba1e1d5 100644 --- a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.hxx +++ b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.hxx @@ -82,12 +82,9 @@ template bool MCMLinearInterpolateImageFunction< TInputImage, TCoordRep > ::CheckModelCompatibility(MCModelPointer &model) -{ - unsigned int numIsoCompartments = model->GetNumberOfIsotropicCompartments(); - if (!model->GetCompartment(numIsoCompartments)->GetTensorCompatible()) - return false; - - return true; +{ + unsigned int numIsoCompartments = model->GetNumberOfIsotropicCompartments(); + return !((model->GetCompartment(numIsoCompartments)->GetCompartmentType() != anima::DDI) && (!model->GetCompartment(numIsoCompartments)->GetTensorCompatible())); } template @@ -119,6 +116,17 @@ MCMLinearInterpolateImageFunction< TInputImage, TCoordRep > } +template +void +MCMLinearInterpolateImageFunction< TInputImage, TCoordRep > +::SetSpecificAveragerParameters(unsigned int threadIndex) const +{ + typedef anima::MCMWeightedAverager InternalAveragerType; + InternalAveragerType *castAverager = dynamic_cast (this->GetAveragers()[threadIndex].GetPointer()); + + castAverager->SetDDIInterpolationMethod(m_DDIInterpolationMethod); +} + template unsigned int MCMLinearInterpolateImageFunction< TInputImage, TCoordRep > diff --git a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx deleted file mode 100644 index 0eabab11d..000000000 --- a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.cxx +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include - -namespace anima -{ - -MCMDDIWeightedAverager::MCMDDIWeightedAverager() -{ - m_DDIInterpolationMethod = 3; -} - -void MCMDDIWeightedAverager::SetDDIInterpolationMethod(unsigned int method) -{ - if (m_DDIInterpolationMethod == method) - return; - - m_DDIInterpolationMethod = method; - this->SetUpToDate(false); -} - -void MCMDDIWeightedAverager::ComputeNonTensorDistanceMatrix() -{ - if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) - itkExceptionMacro("Only DDI supported in addition to tensor") - - unsigned int numCompartments = m_WorkCompartmentsVector.size(); - m_InternalDDIKappa.resize(numCompartments); - m_InternalDDINu.resize(numCompartments); - m_InternalDDIDiffusivity.resize(numCompartments); - m_InternalDDIDirections.resize(numCompartments); - vnl_vector tmpVec(3); - tmpVec.fill(0); - for (unsigned int i = 0;i < numCompartments;++i) - m_InternalDDIDirections[i] = tmpVec; - - for (unsigned int i = 0;i < numCompartments;++i) - { - anima::TransformSphericalToCartesianCoordinates(m_WorkCompartmentsVector[i]->GetOrientationTheta(), - m_WorkCompartmentsVector[i]->GetOrientationPhi(),1.0, - m_InternalDDIDirections[i]); - - m_InternalDDIKappa[i] = m_WorkCompartmentsVector[i]->GetOrientationConcentration(); - m_InternalDDIDiffusivity[i] = m_WorkCompartmentsVector[i]->GetAxialDiffusivity(); - m_InternalDDINu[i] = m_WorkCompartmentsVector[i]->GetExtraAxonalFraction(); - } - - anima::ComputeDistanceMatrixBetweenFascicles(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, - m_DDIInterpolationMethod,m_InternalDistanceMatrix); -} - -void MCMDDIWeightedAverager::ComputeOutputNonTensorModel() -{ - if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) - itkExceptionMacro("Only DDI supported in addition to tensor") - - std::vector referenceDDIWeights = m_WorkCompartmentWeights; - - double averageNu = 0,averageDiffusivity = 0,averageKappa = 0,averageWeight = 0; - vnl_vector averageDirection(3); - - unsigned int nbOfUsefulFascicles = m_WorkCompartmentWeights.size(); - unsigned int numIsoCompartments = this->GetUntouchedOutputModel()->GetNumberOfIsotropicCompartments(); - unsigned int numberOfOutputCompartments = m_InternalSpectralMemberships[0].size(); - - for (unsigned int i = 0;i < numberOfOutputCompartments;++i) - { - for (unsigned int j = 0;j < nbOfUsefulFascicles;++j) - m_WorkCompartmentWeights[j] = referenceDDIWeights[j] * m_InternalSpectralMemberships[j][i]; - - anima::DDIAveraging(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, - m_WorkCompartmentWeights,m_DDIInterpolationMethod,averageNu,averageDiffusivity,averageKappa, - averageDirection,averageWeight); - - anima::BaseCompartment *workCompartment = this->GetUntouchedOutputModel()->GetCompartment(i+numIsoCompartments); - workCompartment->SetAxialDiffusivity(averageDiffusivity); - workCompartment->SetOrientationConcentration(averageKappa); - workCompartment->SetExtraAxonalFraction(averageNu); - anima::TransformCartesianToSphericalCoordinates(averageDirection,averageDirection); - workCompartment->SetOrientationTheta(averageDirection[0]); - workCompartment->SetOrientationPhi(averageDirection[1]); - - m_InternalOutputWeights[i+numIsoCompartments] = averageWeight; - } -} - -} // end namespace anima diff --git a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h b/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h deleted file mode 100644 index 5d7e5b167..000000000 --- a/Anima/math-tools/multi_compartment_base/animaMCMDDIWeightedAverager.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -#include -#include -#include - -#include - -#include - -namespace anima -{ - -/** - * @brief Computes a weighted average of input multi-compartment models including DDI. The output model is at the same - * time giving the number and type of output compartments but also its parameters are erased when performing Update - * to get the result - */ -class ANIMAMCMBASE_EXPORT MCMDDIWeightedAverager : public anima::MCMWeightedAverager -{ -public: - typedef MCMDDIWeightedAverager Self; - typedef anima::MCMWeightedAverager Superclass; - typedef itk::SmartPointer Pointer; - typedef itk::SmartPointer ConstPointer; - - /** Run-time type information (and related methods) */ - itkTypeMacro(MCMDDIWeightedAverager, anima::MCMWeightedAverager) - - itkNewMacro(Self) - - void SetDDIInterpolationMethod(unsigned int method); - -protected: - MCMDDIWeightedAverager(); - ~MCMDDIWeightedAverager() {} - - void ComputeNonTensorDistanceMatrix() ITK_OVERRIDE; - void ComputeOutputNonTensorModel() ITK_OVERRIDE; - -private: - unsigned int m_DDIInterpolationMethod; - - // Internal work variables - std::vector m_InternalDDIKappa, m_InternalDDINu, m_InternalDDIDiffusivity; - std::vector < vnl_vector > m_InternalDDIDirections; -}; - -} // end namespace anima diff --git a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx index 4c79b7d6a..efa3cff96 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx +++ b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx @@ -9,6 +9,8 @@ MCMWeightedAverager::MCMWeightedAverager() m_NumberOfOutputDirectionalCompartments = 3; m_InternalEigenAnalyzer.SetDimension(3); m_InternalEigenAnalyzer.SetOrder(3); + + m_DDIInterpolationMethod = 3; m_InternalSpectralCluster.SetMaxIterations(200); m_InternalSpectralCluster.SetCMeansAverageType(SpectralClusterType::CMeansFilterType::Euclidean); @@ -29,6 +31,16 @@ void MCMWeightedAverager::SetNumberOfOutputDirectionalCompartments(unsigned int m_UpToDate = false; } + +void MCMDDIWeightedAverager::SetDDIInterpolationMethod(unsigned int method) +{ + if (m_DDIInterpolationMethod == method) + return; + + m_DDIInterpolationMethod = method; + this->SetUpToDate(false); +} + void MCMWeightedAverager::ResetNumberOfOutputDirectionalCompartments() { m_NumberOfOutputDirectionalCompartments = m_OutputModel->GetNumberOfCompartments() - m_OutputModel->GetNumberOfIsotropicCompartments(); @@ -201,7 +213,32 @@ void MCMWeightedAverager::ComputeTensorDistanceMatrix() void MCMWeightedAverager::ComputeNonTensorDistanceMatrix() { - itkExceptionMacro("No non-tensor distance matrix implemented in public version") + if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) + itkExceptionMacro("Only DDI supported in addition to tensor") + + unsigned int numCompartments = m_WorkCompartmentsVector.size(); + m_InternalDDIKappa.resize(numCompartments); + m_InternalDDINu.resize(numCompartments); + m_InternalDDIDiffusivity.resize(numCompartments); + m_InternalDDIDirections.resize(numCompartments); + vnl_vector tmpVec(3); + tmpVec.fill(0); + for (unsigned int i = 0;i < numCompartments;++i) + m_InternalDDIDirections[i] = tmpVec; + + for (unsigned int i = 0;i < numCompartments;++i) + { + anima::TransformSphericalToCartesianCoordinates(m_WorkCompartmentsVector[i]->GetOrientationTheta(), + m_WorkCompartmentsVector[i]->GetOrientationPhi(),1.0, + m_InternalDDIDirections[i]); + + m_InternalDDIKappa[i] = m_WorkCompartmentsVector[i]->GetOrientationConcentration(); + m_InternalDDIDiffusivity[i] = m_WorkCompartmentsVector[i]->GetAxialDiffusivity(); + m_InternalDDINu[i] = m_WorkCompartmentsVector[i]->GetExtraAxonalFraction(); + } + + anima::ComputeDistanceMatrixBetweenFascicles(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, + m_DDIInterpolationMethod,m_InternalDistanceMatrix); } void MCMWeightedAverager::ComputeOutputTensorCompatibleModel() @@ -279,7 +316,37 @@ void MCMWeightedAverager::ComputeOutputTensorCompatibleModel() void MCMWeightedAverager::ComputeOutputNonTensorModel() { - itkExceptionMacro("No non-tensor model computation implemented in public version") + if (m_WorkCompartmentsVector[0]->GetCompartmentType() != anima::DDI) + itkExceptionMacro("Only DDI supported in addition to tensor") + + std::vector referenceDDIWeights = m_WorkCompartmentWeights; + + double averageNu = 0,averageDiffusivity = 0,averageKappa = 0,averageWeight = 0; + vnl_vector averageDirection(3); + + unsigned int nbOfUsefulFascicles = m_WorkCompartmentWeights.size(); + unsigned int numIsoCompartments = this->GetUntouchedOutputModel()->GetNumberOfIsotropicCompartments(); + unsigned int numberOfOutputCompartments = m_InternalSpectralMemberships[0].size(); + + for (unsigned int i = 0;i < numberOfOutputCompartments;++i) + { + for (unsigned int j = 0;j < nbOfUsefulFascicles;++j) + m_WorkCompartmentWeights[j] = referenceDDIWeights[j] * m_InternalSpectralMemberships[j][i]; + + anima::DDIAveraging(m_InternalDDINu,m_InternalDDIDiffusivity,m_InternalDDIKappa,m_InternalDDIDirections, + m_WorkCompartmentWeights,m_DDIInterpolationMethod,averageNu,averageDiffusivity,averageKappa, + averageDirection,averageWeight); + + anima::BaseCompartment *workCompartment = this->GetUntouchedOutputModel()->GetCompartment(i+numIsoCompartments); + workCompartment->SetAxialDiffusivity(averageDiffusivity); + workCompartment->SetOrientationConcentration(averageKappa); + workCompartment->SetExtraAxonalFraction(averageNu); + anima::TransformCartesianToSphericalCoordinates(averageDirection,averageDirection); + workCompartment->SetOrientationTheta(averageDirection[0]); + workCompartment->SetOrientationPhi(averageDirection[1]); + + m_InternalOutputWeights[i+numIsoCompartments] = averageWeight; + } } } // end namespace anima diff --git a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.h b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.h index 862b6eae6..493c23bac 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.h +++ b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.h @@ -44,8 +44,10 @@ class ANIMAMCMBASE_EXPORT MCMWeightedAverager : public itk::LightObject void SetInputModels(std::vector &models) {m_InputModels = models; m_UpToDate = false;} void SetInputWeights(std::vector &weights) {m_InputWeights = weights; m_UpToDate = false;} - - void SetNumberOfOutputDirectionalCompartments(unsigned int val); + + void SetDDIInterpolationMethod(unsigned int method); + + void SetNumberOfOutputDirectionalCompartments(unsigned int val); void ResetNumberOfOutputDirectionalCompartments(); void SetOutputModel(MCMType *model); @@ -63,21 +65,21 @@ class ANIMAMCMBASE_EXPORT MCMWeightedAverager : public itk::LightObject ~MCMWeightedAverager() {} void ComputeTensorDistanceMatrix(); - virtual void ComputeNonTensorDistanceMatrix(); + void ComputeNonTensorDistanceMatrix(); void ComputeOutputTensorCompatibleModel(); - virtual void ComputeOutputNonTensorModel(); + void ComputeOutputNonTensorModel(); private: std::vector m_InputModels; std::vector m_InputWeights; unsigned int m_NumberOfOutputDirectionalCompartments; + unsigned int m_DDIInterpolationMethod; MCMPointer m_OutputModel; bool m_UpToDate; - -protected: + // Internal work variables std::vector < itk::VariableLengthVector > m_InternalLogTensors; @@ -86,6 +88,9 @@ class ANIMAMCMBASE_EXPORT MCMWeightedAverager : public itk::LightObject std::vector m_WorkCompartmentWeights; vnl_matrix m_InternalDistanceMatrix; std::vector < std::vector > m_InternalSpectralMemberships; + + std::vector m_InternalDDIKappa, m_InternalDDINu, m_InternalDDIDiffusivity; + std::vector < vnl_vector > m_InternalDDIDirections; vnl_matrix m_InternalWorkMatrix, m_InternalWorkEigenVectors; vnl_diag_matrix m_InternalWorkEigenValues, m_InternalWorkEigenValuesInputSticks; diff --git a/Anima/math-tools/statistical_distributions/CMakeLists.txt b/Anima/math-tools/statistical_distributions/CMakeLists.txt index 7e661a2d9..8d86d9d5d 100644 --- a/Anima/math-tools/statistical_distributions/CMakeLists.txt +++ b/Anima/math-tools/statistical_distributions/CMakeLists.txt @@ -41,3 +41,9 @@ generate_export_header(${PROJECT_NAME} ## ############################################################################# set_lib_install_rules(${PROJECT_NAME}) + + +## ############################################################################# +## Add subdirectory for an executable +## ############################################################################# +add_subdirectory(sample_image_from_distribution) \ No newline at end of file diff --git a/Anima/math-tools/statistical_distributions/animaDDIDistribution.h b/Anima/math-tools/statistical_distributions/animaDDIDistribution.h new file mode 100644 index 000000000..b710e2933 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaDDIDistribution.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace anima +{ + +template +double +ComputeAsymmetricPDF(const std::vector &w, const std::vector &mu, + const ScalarType &kappa, const ScalarType &d, const ScalarType &nu, + const ScalarType &step, std::vector &integrand); + +template +double ComputeSymmetricPDF(const VectorType &w, const VectorType &mu, + const ScalarType &kappa, const ScalarType &d, const ScalarType &nu, + const ScalarType &step, std::vector &integrand); + +template +double ComputeSymmetricCDF(const VectorType &direction, const T1 &kappa, const T1 &lambda, + const T1 &nu, const T1 &bvalue, const VectorType &gradient); + +template +double ComputeMixtureSymmetricCDF(const unsigned int &NbComponents, + const T2 *Directions, const T1 *Kappa, + const T1 *Lambda, const T1 *Nu, const T1 *W, + const T1 &b, const VectorType &grad); + +} // end of namespace anima + +#include "animaDDIDistribution.hxx" diff --git a/Anima/math-tools/statistical_distributions/animaDDIDistribution.hxx b/Anima/math-tools/statistical_distributions/animaDDIDistribution.hxx new file mode 100644 index 000000000..a17bc5b7d --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaDDIDistribution.hxx @@ -0,0 +1,274 @@ +#pragma once + +#include "animaDDIDistribution.h" + +#include +#include +#include +#include + +#include + +namespace anima +{ + +template double ComputeAsymmetricPDF(const std::vector &w, const std::vector &mu, + const ScalarType &kappa, const ScalarType &d, const ScalarType &nu, + const ScalarType &step, std::vector &integrand) +{ + double invNu = 1.0 - nu; + + if (invNu < 0.0) + invNu = 0.0; + + if (invNu > 1.0) + invNu = 1.0; + + if (invNu < 1e-4 || d < 1e-16) + return 0; + + double rNu = nu / invNu; + double gaussDenom = 2.0*invNu*d; + double C = (kappa + 1.0) / std::pow(gaussDenom*M_PI,1.5); + double wNorm = anima::ComputeNorm(w); + double sphereRadius = std::sqrt(nu * d); + + double resVal; + + if (nu < 1e-8) + { + double wpara = anima::ComputeScalarProduct(mu, w); + double wparaSq = wpara * wpara; + double wperpSq = wNorm * wNorm - wparaSq; + resVal = C * exp(-((kappa + 1.0)*wperpSq+wparaSq)/gaussDenom); + } + else + { + if (kappa < 1e-4) + { + wNorm /= sphereRadius; + + C *= ((1.0 - std::exp(-2.0*rNu*wNorm)) / (2.0*rNu*wNorm)); + resVal = C * std::exp(-rNu*wNorm*wNorm/2.0 + +rNu*wNorm + -rNu/2.0); + } + else + { + C *= (kappa / (1.0 - exp(-2.0*kappa))); + + double wpara = anima::ComputeScalarProduct(mu, w); + double wparaSq = wpara * wpara; + double wperpSq = wNorm * wNorm - wparaSq; + + if (wperpSq < 0) + wperpSq = 0; + + double wperp = std::sqrt(wperpSq); + + // Integral over -1 to 1 is in fact done from 0 to 1 with a cosh, then doubled to even things out + for (unsigned int i = 0;i < integrand.size();++i) + { + double t = i*step; + + integrand[i] = rNu*kappa*t*t/2.0 + + anima::log_bessel_i(0, rNu*(kappa + 1.0)*wperp*std::sqrt(1-t*t) / sphereRadius) + - kappa + - ((kappa + 1.0)*wperpSq + wparaSq) / gaussDenom + - rNu*(kappa + 1.0)/2.0; + + double internalSinhVal = (kappa + rNu*wpara / sphereRadius) * t; + if (std::abs(internalSinhVal) > 50) + integrand[i] += std::abs(internalSinhVal) - std::log(2.0); + else + integrand[i] += std::log(std::cosh(internalSinhVal)); + + integrand[i] = std::exp(integrand[i]); + } + + double integral = 0; + for (unsigned int i = 0;i < integrand.size() - 1;++i) + integral += step * (integrand[i] + integrand[i+1])/2.0; + + resVal = 2.0 * C * integral; + } + } + + return resVal; +} + +template double ComputeSymmetricPDF(const VectorType &w, const VectorType &mu, + const ScalarType &kappa, const ScalarType &d, const ScalarType &nu, + const ScalarType &step, std::vector &integrand) +{ + double invNu = 1.0 - nu; + + if (invNu < 0.0) + invNu = 0.0; + + if (invNu > 1.0) + invNu = 1.0; + + if (invNu < 1e-4 || d < 1e-16) + return 0; + + double rNu = nu / invNu; + double gaussDenom = 2.0*invNu*d; + double C = (kappa + 1.0) / pow(gaussDenom*M_PI,1.5); + double wNorm = anima::ComputeNorm(w); + double sphereRadius = std::sqrt(nu * d); + + double resVal; + + if (nu < 1e-8) + { + double wpara = anima::ComputeScalarProduct(mu, w); + double wparaSq = wpara * wpara; + double wperpSq = wNorm * wNorm - wparaSq; + resVal = C * exp(-((kappa + 1.0)*wperpSq+wparaSq)/gaussDenom); + } + else + { + if (kappa < 1e-4) + { + wNorm /= sphereRadius; + + C *= ((1.0 - std::exp(-2.0*rNu*wNorm)) / (2.0*rNu*wNorm)); + resVal = C * std::exp(-rNu*wNorm*wNorm/2.0 + +rNu*wNorm + -rNu/2.0); + } + else + { + C *= (kappa / (1.0 - exp(-2.0*kappa))); + + double wpara = anima::ComputeScalarProduct(mu, w); + double wparaSq = wpara * wpara; + double wperpSq = wNorm * wNorm - wparaSq; + + if (wperpSq < 0) + wperpSq = 0; + + double wperp = std::sqrt(wperpSq); + + // Integral over -1 to 1 is in fact done from 0 to 1 with a cosh, then doubled to even things out + // That plus symmetrization leads to two cosh values + for (unsigned int i = 0;i < integrand.size();++i) + { + double t = i*step; + + integrand[i] = rNu*kappa*t*t/2.0 + + anima::log_bessel_i(0, rNu*(kappa + 1.0)*wperp*std::sqrt(1-t*t) / sphereRadius) + - kappa + - ((kappa + 1.0)*wperpSq + wparaSq) / gaussDenom + - rNu*(kappa + 1.0)/2.0; + + if (std::abs(rNu*wpara*t / sphereRadius) > 50) + integrand[i] += std::abs(rNu*wpara*t / sphereRadius) - std::log(2.0); + else + integrand[i] += std::log(std::cosh(rNu*wpara*t / sphereRadius)); + + if (std::abs(kappa*t) > 50) + integrand[i] += std::abs(kappa*t) - std::log(2.0); + else + integrand[i] += std::log(std::cosh(kappa*t)); + + integrand[i] = std::exp(integrand[i]); + } + + double integral = 0; + for (unsigned int i = 0;i < integrand.size() - 1;++i) + integral += step * (integrand[i] + integrand[i+1])/2.0; + + resVal = 2.0 * C * integral; + } + } + + return resVal; +} + +template double ComputeSymmetricCDF(const VectorType &direction, const T1 &kappa, const T1 &lambda, const T1 &nu, + const T1 &bvalue, const VectorType &gradient) +{ + double resVal; + + double vmfLambda = nu * lambda; + double gaussLambda = (1.0 - nu) * lambda; + + double muG = anima::ComputeScalarProduct(direction, gradient); + + resVal = std::exp( -bvalue * gaussLambda * (1 + kappa * muG * muG) / (kappa + 1) ); + + double sqrt2bLambda = std::sqrt(2.0 * bvalue * vmfLambda); + double kappaSq = kappa * kappa; + + double ReZ = kappaSq - sqrt2bLambda * sqrt2bLambda; + double ImZ = 2.0 * kappa * sqrt2bLambda * muG; + + double condition = ReZ + std::sqrt(ReZ*ReZ + ImZ*ImZ); + if (condition < 0) + condition = 0; + bool omega = condition < 1e-2; + + if (omega) + { + if (ReZ > 0) + ReZ = 0; + resVal *= (anima::SinOverId(sqrt(-ReZ)) / anima::ShOverId(kappa)); + } + else + { + double alpha = std::sqrt(condition / 2.0); + double beta = ImZ / std::sqrt(2.0 * condition); + double tmp = anima::ShRatio(kappa, alpha, beta); + resVal *= tmp; + } + + return resVal; +} + +template double ComputeMixtureSymmetricCDF(const unsigned int &NbComponents, + const T2 *Directions, const T1 *Kappa, + const T1 *Lambda, const T1 *Nu, const T1 *W, + const T1 &b, const VectorType &grad) +{ + /* Isotropic component */ + double w0 = W[NbComponents]; + double lambda_iso = Lambda[NbComponents]; + double nu_iso = Nu[NbComponents]; + + double resVal = 0; + + if (nu_iso == 0) + resVal += w0 * std::exp(-b * lambda_iso); + else + resVal += w0 * std::exp(-b * (1.0 - nu_iso) * lambda_iso) * anima::SinOverId(sqrt(2.0 * b * nu_iso * lambda_iso)); + + /* Other NbComponents component(s) of the mixture */ + for (unsigned int i = 0; i < NbComponents; ++i) + { + double w = W[i]; + + if (w > 0) + { + double DDISingle = 0; + + if (Nu[i] == 0) + { + double cosAngle = anima::ComputeScalarProduct(grad,Directions[i]); + double kVal = Kappa[i]; + DDISingle = std::exp( -b * Lambda[i] / (kVal + 1.0) * (1.0 + cosAngle * cosAngle * kVal) ); + } + else + { + DDISingle = ComputeSymmetricCDF(Directions[i], Kappa[i], Lambda[i], Nu[i], b, grad); + } + + resVal += w * DDISingle; + } + } + + return std::abs(resVal); +} + +} // end namespace ddi_distribution diff --git a/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h b/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h new file mode 100644 index 000000000..50bccb90f --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.h @@ -0,0 +1,56 @@ +#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 new file mode 100644 index 000000000..0b8d3fb98 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/animaSampleImageFromDistributionImageFilter.hxx @@ -0,0 +1,119 @@ +#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/sample_image_from_distribution/CMakeLists.txt b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt new file mode 100644 index 000000000..1a9c50218 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaSampleImageFromDistribution) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() 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 new file mode 100644 index 000000000..ad4167b95 --- /dev/null +++ b/Anima/math-tools/statistical_distributions/sample_image_from_distribution/animaSampleImageFromDistribution.cxx @@ -0,0 +1,58 @@ +#include + +#include +#include + +#include + +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); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef anima::SampleImageFromDistributionImageFilter MainFilterType; + + typedef itk::ImageFileReader < MainFilterType::TInputImage > ImageReaderType; + typedef itk::ImageFileWriter < MainFilterType::TOutputImage > ImageWriterType; + + MainFilterType::Pointer mainFilter = MainFilterType::New(); + + ImageReaderType::Pointer meanReader = ImageReaderType::New(); + meanReader->SetFileName(meanArg.getValue()); + meanReader->Update(); + mainFilter->SetInput(0,meanReader->GetOutput()); + + ImageReaderType::Pointer covReader = ImageReaderType::New(); + covReader->SetFileName(covArg.getValue()); + covReader->Update(); + 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; +} From f09252637200f2781e6a2eef5428e876656f6806 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 11:28:13 +0200 Subject: [PATCH 04/17] Add some files needed to compile --- .../animaModularityClusteringFilter.h | 58 +++ .../animaModularityClusteringFilter.hxx | 250 +++++++++++ .../clustering/animaSPANClusteringFilter.h | 63 +++ .../clustering/animaSPANClusteringFilter.hxx | 422 ++++++++++++++++++ 4 files changed, 793 insertions(+) create mode 100644 Anima/filtering/clustering/animaModularityClusteringFilter.h create mode 100644 Anima/filtering/clustering/animaModularityClusteringFilter.hxx create mode 100644 Anima/filtering/clustering/animaSPANClusteringFilter.h create mode 100644 Anima/filtering/clustering/animaSPANClusteringFilter.hxx diff --git a/Anima/filtering/clustering/animaModularityClusteringFilter.h b/Anima/filtering/clustering/animaModularityClusteringFilter.h new file mode 100644 index 000000000..8a15c63de --- /dev/null +++ b/Anima/filtering/clustering/animaModularityClusteringFilter.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +namespace anima { + + template + class ModularityClusteringFilter + { + public: + typedef vnl_matrix DataHolderType; + typedef std::vector < unsigned int > MembershipType; + typedef std::vector < MembershipType > MembershipVectorType; + typedef vnl_vector < double > VectorType; + typedef vnl_matrix < double > MatrixType; + + ModularityClusteringFilter(); + virtual ~ModularityClusteringFilter(); + + void SetInputData(DataHolderType &data); + void Update(); + + unsigned int GetClassMembership(unsigned int i) {return m_ClassMemberships[i];} + MembershipType& GetClassMemberships() {return m_ClassMemberships;} + MembershipType& GetReverseClassMembership(unsigned int i) {return m_ReverseClassMemberships[i];} + MembershipVectorType& GetReverseClassMemberships() {return m_ReverseClassMemberships;} + + unsigned int GetNumberOfClusters() {return m_NumberOfClusters;} + + private: + void InitializeClassMemberships(); + void GetModularityMatrix(); + void GetSubModularityMatrix(unsigned int parentIndex); + double SpectralClustering(unsigned int parentIndex); + void SplitCluster(unsigned int parentIndex); + + MembershipType m_ClassMemberships, m_Membership, m_SubMembership1, m_SubMembership2; + MembershipVectorType m_ReverseClassMemberships; + MatrixType m_ConnectivityMatrix, m_ModularityMatrix, m_SubModularityMatrix; + VectorType m_DegreeMatrix, m_SubDegreeMatrix; + + std::vector < bool > m_PreventClusterFromSplitting; + + unsigned int m_NbInputs; + unsigned int m_NumberOfClusters; + + double m_TotalNumberOfEdges; + + static const double m_ZeroThreshold; + }; + +} // end namespace anima + +#include "animaModularityClusteringFilter.hxx" + + diff --git a/Anima/filtering/clustering/animaModularityClusteringFilter.hxx b/Anima/filtering/clustering/animaModularityClusteringFilter.hxx new file mode 100644 index 000000000..e107a4a12 --- /dev/null +++ b/Anima/filtering/clustering/animaModularityClusteringFilter.hxx @@ -0,0 +1,250 @@ +#pragma once + +#include "animaModularityClusteringFilter.h" + +#include + +namespace anima { + +template +const double ModularityClusteringFilter ::m_ZeroThreshold = 1.0e-6; + +template +ModularityClusteringFilter :: +ModularityClusteringFilter() +{ + m_ClassMemberships.clear(); + m_ReverseClassMemberships.clear(); + m_ConnectivityMatrix.clear(); + m_ModularityMatrix.clear(); + m_SubModularityMatrix.clear(); + m_DegreeMatrix.clear(); + m_SubDegreeMatrix.clear(); + m_PreventClusterFromSplitting.clear(); + m_SubMembership1.clear(); + m_SubMembership2.clear(); + m_Membership.clear(); + + m_NbInputs = 0; + m_NumberOfClusters = 0; + + m_TotalNumberOfEdges = 0.0; +} + +template +ModularityClusteringFilter :: +~ModularityClusteringFilter() +{ +} + +template +void +ModularityClusteringFilter :: +SetInputData(DataHolderType &data) +{ + if (data.rows() == 0) + return; + + m_ConnectivityMatrix = data; + + m_NbInputs = m_ConnectivityMatrix.rows(); + + m_TotalNumberOfEdges = 0.0; + m_DegreeMatrix.set_size(m_NbInputs); + + for (unsigned int i = 0;i < m_NbInputs;++i) + { + double t = m_ConnectivityMatrix.get_row(i).sum(); + m_DegreeMatrix(i) = t; + m_TotalNumberOfEdges += t; + } + + m_TotalNumberOfEdges /= 2.0; +} + +template +void +ModularityClusteringFilter :: +InitializeClassMemberships() +{ + m_NumberOfClusters = 1; + + m_ClassMemberships.resize(m_NbInputs); + m_ReverseClassMemberships.resize(m_NumberOfClusters); + m_ReverseClassMemberships[0].resize(m_NbInputs); + for (unsigned int i = 0;i < m_NbInputs;++i) + { + m_ClassMemberships[i] = 0; + m_ReverseClassMemberships[0][i] = i; + } + + m_PreventClusterFromSplitting.resize(m_NumberOfClusters); + m_PreventClusterFromSplitting[0] = false; +} + +template +void +ModularityClusteringFilter :: +GetModularityMatrix() +{ + m_ModularityMatrix.set_size(m_NbInputs,m_NbInputs); + + for (unsigned int i = 0;i < m_NbInputs;++i) + { + for (unsigned int j = i;j < m_NbInputs;++j) + { + double t = m_ConnectivityMatrix(i,j) - m_DegreeMatrix(i) * m_DegreeMatrix(j) / (2.0 * m_TotalNumberOfEdges); + m_ModularityMatrix(i,j) = t; + + if (i != j) + m_ModularityMatrix(j,i) = t; + } + } +} + +template +void +ModularityClusteringFilter :: +GetSubModularityMatrix(unsigned int parentIndex) +{ + m_Membership = m_ReverseClassMemberships[parentIndex]; + unsigned int numData = m_Membership.size(); + + m_SubDegreeMatrix.set_size(numData); + m_SubDegreeMatrix.fill(0.0); + + for (unsigned int i = 0;i < numData;++i) + for (unsigned int j = 0;j < numData;++j) + m_SubDegreeMatrix(i) += m_ModularityMatrix(m_Membership[i],m_Membership[j]); + + m_SubModularityMatrix.set_size(numData,numData); + + for (unsigned int i = 0;i < numData;++i) + { + for (unsigned int j = i;j < numData;++j) + { + double t = m_ModularityMatrix(m_Membership[i],m_Membership[j]); + + m_SubModularityMatrix(i,j) = t; + + if (i == j) + m_SubModularityMatrix(i,j) -= m_SubDegreeMatrix(i); + else + m_SubModularityMatrix(j,i) = t; + } + } +} + +template +double +ModularityClusteringFilter :: +SpectralClustering(unsigned int parentIndex) +{ + m_Membership = m_ReverseClassMemberships[parentIndex]; + unsigned int numData = m_Membership.size(); + + // Data for eigen analysis + typedef itk::SymmetricEigenAnalysis eigenAnalysis; + MatrixType eigVecs(numData,numData); + VectorType eigVals(numData); + + this->GetSubModularityMatrix(parentIndex); + + eigenAnalysis eigSystem(numData); + eigSystem.SetOrderEigenValues(true); + eigSystem.ComputeEigenValuesAndVectors(m_SubModularityMatrix, eigVals, eigVecs); + + VectorType tmpVec = eigVecs.get_row(numData-1); + + for (unsigned int i = 0;i < numData;++i) + { + unsigned int currentParticle = m_Membership[i]; + + if (tmpVec(i) < 0) + m_SubMembership1.push_back(currentParticle); + else + m_SubMembership2.push_back(currentParticle); + } + + // Compute modularity for splitting this cluster into two subclusters + double s_i, s_j, Q = 0; + for (unsigned int i = 0;i < numData;++i) + { + if (tmpVec(i) < 0) + s_i = -1; + else + s_i = 1; + + for (unsigned int j = 0;j < numData;++j) + { + if (tmpVec(j) < 0) + s_j = -1; + else + s_j = 1; + + Q += m_SubModularityMatrix(i,j) * s_i * s_j; + } + } + + Q /= (4.0 * m_TotalNumberOfEdges); + return Q; +} + +template +void +ModularityClusteringFilter :: +SplitCluster(unsigned int parentIndex) +{ + m_Membership = m_ReverseClassMemberships[parentIndex]; + unsigned int numData = m_Membership.size(); + + double Q = 0; + + // perform traditional spectral clustering + m_SubMembership1.clear(); + m_SubMembership2.clear(); + Q = this->SpectralClustering(parentIndex); + + if (Q > m_ZeroThreshold && m_SubMembership1.size() != numData && m_SubMembership2.size() != numData) + { + // Update class memberships + for (unsigned int i = 0;i < m_SubMembership2.size();++i) + m_ClassMemberships[m_SubMembership2[i]] = m_NumberOfClusters; + + m_ReverseClassMemberships[parentIndex] = m_SubMembership1; + m_ReverseClassMemberships.push_back(m_SubMembership2); + ++m_NumberOfClusters; + + m_PreventClusterFromSplitting.push_back(false); + } + else + m_PreventClusterFromSplitting[parentIndex] = true; +} + +template +void +ModularityClusteringFilter :: +Update() +{ + if (m_NbInputs == 0) + { + std::cerr << "Input data is empty." << std::endl; + return; + } + + this->InitializeClassMemberships(); + this->GetModularityMatrix(); + + unsigned int oldNumberOfClusters = 0; + while (m_NumberOfClusters > oldNumberOfClusters) + { + oldNumberOfClusters = m_NumberOfClusters; + + for (unsigned int i = 0;i < oldNumberOfClusters;++i) + if (!m_PreventClusterFromSplitting[i]) + this->SplitCluster(i); + } +} +} // end namespace anima + + diff --git a/Anima/filtering/clustering/animaSPANClusteringFilter.h b/Anima/filtering/clustering/animaSPANClusteringFilter.h new file mode 100644 index 000000000..ab0fd66e8 --- /dev/null +++ b/Anima/filtering/clustering/animaSPANClusteringFilter.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +namespace anima { + + template + class SPANClusteringFilter + { + public: + typedef std::vector < DataType > DataHolderType; + typedef std::vector < unsigned int > MembershipType; + typedef std::vector < MembershipType > MembershipVectorType; + typedef vnl_vector < double > VectorType; + typedef vnl_matrix < double > MatrixType; + + SPANClusteringFilter(); + virtual ~SPANClusteringFilter(); + + void SetInputData(DataHolderType &data); + void SetOrientationData(bool orientationData) {m_OrientationData = orientationData;} + void Update(); + + unsigned int GetClassMembership(unsigned int i) {return m_ClassMemberships[i];} + MembershipType& GetClassMemberships() {return m_ClassMemberships;} + MembershipType& GetReverseClassMembership(unsigned int i) {return m_ReverseClassMemberships[i];} + MembershipVectorType& GetReverseClassMemberships() {return m_ReverseClassMemberships;} + + unsigned int GetNumberOfClusters() {return m_NumberOfClusters;} + + private: + void InitializeClassMemberships(); + MatrixType ComputeCMatrix(unsigned int parentClusterIndex, VectorType &BtPerOne); + void FastBipartioning(MatrixType &data, unsigned int parentIndex, MembershipType &members1, MembershipType &members2); + double GetNewmanGirvanModularity(MembershipType &members1, MembershipType &members2, VectorType &BtPerOne); + void SplitCluster(unsigned int parentCluster); + void GetConnectivityMatrix(); + void GetModularityMatrix(); + MatrixType GetSubModularityMatrix(unsigned int parentIndex); + double SpectralClustering(unsigned int parentIndex, MembershipType &members1, MembershipType &members2); + + MembershipType m_ClassMemberships; + MembershipVectorType m_ReverseClassMemberships; + DataHolderType m_InputData; + MatrixType m_ConnectivityMatrix, m_ModularityMatrix; + VectorType m_DegreeMatrix; + + bool m_OrientationData; + std::vector < bool > m_PreventClusterFromSplitting; + + unsigned int m_NbInputs; + unsigned int m_NumberOfClusters; + + double m_TotalNumberOfEdges; + }; + +} // end namespace anima + +#include "animaSPANClusteringFilter.hxx" + + diff --git a/Anima/filtering/clustering/animaSPANClusteringFilter.hxx b/Anima/filtering/clustering/animaSPANClusteringFilter.hxx new file mode 100644 index 000000000..068a578d7 --- /dev/null +++ b/Anima/filtering/clustering/animaSPANClusteringFilter.hxx @@ -0,0 +1,422 @@ +#pragma once + +#include "animaSPANClusteringFilter.h" + +#include +#include +//#include + +namespace anima { + + template + SPANClusteringFilter :: + SPANClusteringFilter() + { + m_ClassMemberships.clear(); + m_ReverseClassMemberships.clear(); + m_InputData.clear(); + m_ConnectivityMatrix.clear(); + m_ModularityMatrix.clear(); + m_DegreeMatrix.clear(); + m_PreventClusterFromSplitting.clear(); + + m_OrientationData = false; + + m_NbInputs = 0; + m_NumberOfClusters = 0; + + m_TotalNumberOfEdges = 0.0; + } + + template + SPANClusteringFilter :: + ~SPANClusteringFilter() + { + } + + template + void + SPANClusteringFilter :: + SetInputData(DataHolderType &data) + { + if (data.size() == 0) + return; + + m_InputData = data; + + m_NbInputs = m_InputData.size(); + } + + template + void + SPANClusteringFilter :: + InitializeClassMemberships() + { + m_NumberOfClusters = 1; + + m_ClassMemberships.resize(m_NbInputs); + m_ReverseClassMemberships.resize(m_NumberOfClusters); + m_ReverseClassMemberships[0].resize(m_NbInputs); + for (unsigned int i = 0;i < m_NbInputs;++i) + { + m_ClassMemberships[i] = 0; + m_ReverseClassMemberships[0][i] = i; + } + + m_PreventClusterFromSplitting.resize(m_NumberOfClusters); + m_PreventClusterFromSplitting[0] = false; + } + + template + typename SPANClusteringFilter ::MatrixType + SPANClusteringFilter :: + ComputeCMatrix(unsigned int parentClusterIndex, VectorType &BtPerOne) + { + unsigned int parentClusterSize = m_ReverseClassMemberships[parentClusterIndex].size(); + + MatrixType C(parentClusterSize,DataDimension); + C.fill(0.0); + + BtPerOne.set_size(DataDimension); + BtPerOne.fill(0); + + for (unsigned int j = 0;j < DataDimension;++j) + { + BtPerOne(j) = 0; + for (unsigned int i = 0;i < parentClusterSize;++i) + { + unsigned int currentParticle = m_ReverseClassMemberships[parentClusterIndex][i]; + BtPerOne(j) += m_InputData[currentParticle][j]; + } + } + + for (unsigned int i = 0;i < parentClusterSize;++i) + { + unsigned int currentParticle = m_ReverseClassMemberships[parentClusterIndex][i]; + + double tmpVal = 0; + + for (unsigned int j = 0;j < DataDimension;++j) + tmpVal += m_InputData[currentParticle][j] * BtPerOne(j); + + if (tmpVal <= 0) + { + std::cerr << "SPAN Clustering: negative entries in similarity matrix; this might have also happened before this error displays." << std::endl; + exit(-1); + } + + for (unsigned int j = 0;j < DataDimension;++j) + C(i,j) = m_InputData[currentParticle][j] / std::sqrt(tmpVal); + } + + return C; + } + + template + void + SPANClusteringFilter :: + FastBipartioning(MatrixType &data, unsigned int parentIndex, MembershipType &members1, MembershipType &members2) + { + unsigned int parentClusterSize = data.rows(); + vnl_svd < double > svd(data); + + unsigned int pos = 0; + while (svd.W(pos) == svd.W(0)) + ++pos; + + VectorType tmpVec = svd.U().get_column(pos); + + for (unsigned int i = 0;i < parentClusterSize;++i) + { + unsigned int currentParticle = m_ReverseClassMemberships[parentIndex][i]; + + if (tmpVec(i) < 0) + members1.push_back(currentParticle); + else + members2.push_back(currentParticle); + } + } + + template + double + SPANClusteringFilter :: + GetNewmanGirvanModularity(MembershipType &members1, MembershipType &members2, VectorType &BtPerOne) + { + unsigned int numGrp1 = members1.size(); + unsigned int numGrp2 = members2.size(); + unsigned int numData = numGrp1 + numGrp2; + + double L = -(double)numData, L1 = -(double)numGrp1, L2 = -(double)numGrp2; + double O11 = -(double)numGrp1, O22 = -(double)numGrp2; + + for (unsigned int i = 0;i < DataDimension;++i) + { + L += BtPerOne(i) * BtPerOne(i); + + double tmpVal1 = 0; + for (unsigned int j = 0;j < numGrp1;++j) + { + unsigned int currentParticle = members1[j]; + tmpVal1 += m_InputData[currentParticle][i]; + } + + double tmpVal2 = 0; + for (unsigned int j = 0;j < numGrp2;++j) + { + unsigned int currentParticle = members2[j]; + tmpVal2 += m_InputData[currentParticle][i]; + } + + L1 += tmpVal1 * BtPerOne(i); + L2 += tmpVal2 * BtPerOne(i); + O11 += tmpVal1 * tmpVal1; + O22 += tmpVal2 * tmpVal2; + } + + L1 /= L; + L2 /= L; + O11 /= L; + O22 /= L; + + double resVal = O11 + O22 - (L1 * L1 + L2 * L2); + return resVal; + } + + template + void + SPANClusteringFilter :: + SplitCluster(unsigned int parentCluster) + { + unsigned int parentClusterSize = m_ReverseClassMemberships[parentCluster].size(); + +// if (parentClusterSize == 1) +// { +// m_PreventClusterFromSplitting[parentCluster] = true; +// return; +// } + + MembershipType members1, members2; + double Q = 0; + + if (m_OrientationData) + { + // perform traditional spectral clustering + Q = this->SpectralClustering(parentCluster, members1, members2); + } + else + { + // Get C matrix for the subset B matrix stored in data + VectorType BtPerOne(DataDimension,0); + MatrixType C = this->ComputeCMatrix(parentCluster, BtPerOne); + + // Split data in two according to fast bipartioning method + this->FastBipartioning(C, parentCluster, members1, members2); + + // Compute Newman-Girvan modularity to test validity of the split + Q = this->GetNewmanGirvanModularity(members1, members2, BtPerOne); + } + +// if (Q < -0.5 || Q > 1) +// { +// std::cerr << "Modularity out of bounds: " << Q << std::endl; +// exit(-1); +// } + + if (Q > 1.0e-5 && members1.size() != parentClusterSize && members2.size() != parentClusterSize) + { + // Update class memberships + for (unsigned int i = 0;i < members2.size();++i) + m_ClassMemberships[members2[i]] = m_NumberOfClusters; + + m_ReverseClassMemberships[parentCluster] = members1; + m_ReverseClassMemberships.push_back(members2); + ++m_NumberOfClusters; + + m_PreventClusterFromSplitting.push_back(false); + } + else + m_PreventClusterFromSplitting[parentCluster] = true; + } + + template + void + SPANClusteringFilter :: + Update() + { + if (m_NbInputs == 0) + { + std::cerr << "Input data is empty." << std::endl; + exit(-1); + } + + this->InitializeClassMemberships(); + + if (m_OrientationData) + { + if (m_NbInputs == 2) + { + double t = 0; + for (unsigned int i = 0;i < DataDimension;++i) + t += m_InputData[0][i] * m_InputData[1][i]; + + if (t * t < 0.5) + { + ++m_NumberOfClusters; + m_ClassMemberships[1] = 1; + m_ReverseClassMemberships[0].pop_back(); + m_ReverseClassMemberships.push_back(m_ReverseClassMemberships[0]); + m_ReverseClassMemberships[1][0] = 1; + } + + return; + } + this->GetConnectivityMatrix(); + this->GetModularityMatrix(); + } + + unsigned int oldNumberOfClusters = 0; + while (m_NumberOfClusters > oldNumberOfClusters) + { + oldNumberOfClusters = m_NumberOfClusters; + + for (unsigned int i = 0;i < oldNumberOfClusters;++i) + if (!m_PreventClusterFromSplitting[i]) + this->SplitCluster(i); + } + } + + template + void + SPANClusteringFilter :: + GetConnectivityMatrix() + { + m_ConnectivityMatrix.set_size(m_NbInputs,m_NbInputs); + m_ConnectivityMatrix.fill(0.0); + + m_DegreeMatrix.set_size(m_NbInputs); + m_DegreeMatrix.fill(0.0); + + m_TotalNumberOfEdges = 0.0; + + for (unsigned int i = 0;i < m_NbInputs;++i) + { + for (unsigned int j = i+1;j < m_NbInputs;++j) + { + double t = 0; + for (unsigned int k = 0;k < DataDimension;++k) + t += m_InputData[i][k] * m_InputData[j][k]; + + t *= t; + + m_ConnectivityMatrix(i,j) = t; + m_ConnectivityMatrix(j,i) = t; + m_DegreeMatrix(i) += t; + m_TotalNumberOfEdges += t; + } + + for (unsigned int j = 0;j < i;++j) + m_DegreeMatrix(i) += m_ConnectivityMatrix(j,i); + } + } + + template + void + SPANClusteringFilter :: + GetModularityMatrix() + { + m_ModularityMatrix.set_size(m_NbInputs,m_NbInputs); + + for (unsigned int i = 0;i < m_NbInputs;++i) + { + for (unsigned int j = i;j < m_NbInputs;++j) + { + double t = m_ConnectivityMatrix(i,j) - m_DegreeMatrix(i) * m_DegreeMatrix(j) / (2.0 * m_TotalNumberOfEdges); + m_ModularityMatrix(i,j) = t; + + if (i != j) + m_ModularityMatrix(j,i) = t; + } + } + } + + template + typename SPANClusteringFilter ::MatrixType + SPANClusteringFilter :: + GetSubModularityMatrix(unsigned int parentIndex) + { + MembershipType memberships = m_ReverseClassMemberships[parentIndex]; + unsigned int numData = memberships.size(); + + VectorType subDegreeMatrix(numData,0.0); + + for (unsigned int i = 0;i < numData;++i) + for (unsigned int j = 0;j < numData;++j) + subDegreeMatrix(i) += m_ModularityMatrix(memberships[i],memberships[j]); + + MatrixType B(numData,numData); + + for (unsigned int i = 0;i < numData;++i) + { + for (unsigned int j = i;j < numData;++j) + { + B(i,j) = m_ModularityMatrix(memberships[i],memberships[j]); + + if (i == j) + B(i,j) -= subDegreeMatrix(i); + else + B(j,i) = B(i,j); + } + } + + return B; + } + + template + double + SPANClusteringFilter :: + SpectralClustering(unsigned int parentIndex, MembershipType &members1, MembershipType &members2) + { + unsigned int numData = m_ReverseClassMemberships[parentIndex].size(); + + MatrixType B = this->GetSubModularityMatrix(parentIndex); + + vnl_symmetric_eigensystem eigSystem(B); + VectorType tmpVec = eigSystem.get_eigenvector(numData-1); + + for (unsigned int i = 0;i < numData;++i) + { + unsigned int currentParticle = m_ReverseClassMemberships[parentIndex][i]; + + if (tmpVec(i) < 0) + members1.push_back(currentParticle); + else + members2.push_back(currentParticle); + } + + // Compute modularity for splitting this cluster into two subclusters + double s_i, s_j, Q = 0; + for (unsigned int i = 0;i < numData;++i) + { + if (tmpVec(i) < 0) + s_i = -1; + else + s_i = 1; + + for (unsigned int j = 0;j < numData;++j) + { + if (tmpVec(j) < 0) + s_j = -1; + else + s_j = 1; + + Q += B(i,j) * s_i * s_j; + } + } + + Q /= (4.0 * m_TotalNumberOfEdges); + return Q; + } + +} // end namespace anima + + From 0b78bf9e767615f29345af97551c5b2952a553f7 Mon Sep 17 00:00:00 2001 From: Aymeric Stamm Date: Wed, 12 Apr 2023 12:26:06 +0200 Subject: [PATCH 05/17] Remove mentions to private and reach passing build status --- Anima/diffusion/CMakeLists.txt | 3 +- Anima/diffusion/mcm/CMakeLists.txt | 3 +- .../animaMCMEstimatorImageFilter.hxx | 1 + .../animaMCMAverageImagesImageFilter.hxx | 29 ++----------------- .../mcm_merge_block_images/CMakeLists.txt | 1 - .../animaMCMMergeBlockImages.cxx | 4 +-- .../mt_estimation_validation/CMakeLists.txt | 2 -- .../animaMTEstimationValidation.cxx | 4 +-- .../create_grid_from_pixels/CMakeLists.txt | 1 - .../animaCreateGridFromPixels.cxx | 4 +-- .../ddi_averaging_on_a_grid/CMakeLists.txt | 1 - .../animaDDIAveragingOnAGrid.cxx | 4 +-- .../CMakeLists.txt | 2 -- .../animaDDITestAveragingOnRealValue.cxx | 8 ++--- ...DDITestAveragingOnRealValueImageFilter.cxx | 6 ++-- Anima/diffusion/tractography/CMakeLists.txt | 2 ++ .../CMakeLists.txt | 2 -- .../mcm_tractography/CMakeLists.txt | 3 -- .../animaMCMFileReader.hxx | 1 + .../animaMCMWeightedAverager.cxx | 3 +- 20 files changed, 26 insertions(+), 58 deletions(-) diff --git a/Anima/diffusion/CMakeLists.txt b/Anima/diffusion/CMakeLists.txt index 3ae0803ec..8eeaa7f81 100644 --- a/Anima/diffusion/CMakeLists.txt +++ b/Anima/diffusion/CMakeLists.txt @@ -4,8 +4,7 @@ project(ANIMA-DIFFUSION) # Here go the add_subdirectories, no code should be at the root of the project ################################################################################ -add_subdirectory(dti_estimator) -add_subdirectory(dti_tools) +add_subdirectory(dti) add_subdirectory(mcm) add_subdirectory(mcm_estimator) add_subdirectory(mcm_estimator/low_memory) diff --git a/Anima/diffusion/mcm/CMakeLists.txt b/Anima/diffusion/mcm/CMakeLists.txt index 26f664bd4..4168a54e9 100644 --- a/Anima/diffusion/mcm/CMakeLists.txt +++ b/Anima/diffusion/mcm/CMakeLists.txt @@ -1,4 +1,4 @@ -project(AnimaMCMPrivate) +project(AnimaMCM) ## ############################################################################# ## List Sources @@ -23,7 +23,6 @@ add_library(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} AnimaMCMBase - AnimaMCM AnimaSpecialFunctions ITKOptimizers ITKCommon diff --git a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx index 4525f07c3..2c636f27a 100644 --- a/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx +++ b/Anima/diffusion/mcm_estimator/animaMCMEstimatorImageFilter.hxx @@ -15,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx index 3233b92d9..d60736c8b 100644 --- a/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx +++ b/Anima/diffusion/mcm_tools/mcm_average_images/animaMCMAverageImagesImageFilter.hxx @@ -59,32 +59,9 @@ typename MCMAverageImagesImageFilter ::MCMAveragerPointer MCMAverageImagesImageFilter ::CreateAverager() { - MCMAveragerPointer outAverager = nullptr; - - using InternalAveragerPointer = anima::MCMDDIWeightedAverager; - - bool hasDDICompartment = false; - for(int i=0; iGetNumberOfCompartments() && !hasDDICompartment; ++i) - { - hasDDICompartment = m_ReferenceOutputModel->GetCompartment(i)->GetCompartmentType() == DiffusionModelCompartmentType::DDI; - } - - if(hasDDICompartment) - { - InternalAveragerPointer mcmAverager = InternalAveragerType::New(); - mcmAverager->SetOutputModel(this->GetReferenceOutputModel()); - mcmAverager->SetDDIInterpolationMethod(m_DDIAveragingMethod); - - outAverager = mcmAverager.GetPointer(); - } - else - { - MCMAveragerPointer mcmAverager = anima::MCMWeightedAverager::New(); - mcmAverager->SetOutputModel(m_ReferenceOutputModel); - - outAverager = mcmAverager; - } - + MCMAveragerPointer outAverager = anima::MCMWeightedAverager::New(); + outAverager->SetOutputModel(m_ReferenceOutputModel); + outAverager->SetDDIInterpolationMethod(m_DDIAveragingMethod); return outAverager; } diff --git a/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt b/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt index c47ead76d..e1265807c 100644 --- a/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/mcm_merge_block_images/CMakeLists.txt @@ -24,7 +24,6 @@ add_executable(${PROJECT_NAME} ## ############################################################################# target_link_libraries(${PROJECT_NAME} - AnimaMCMPrivate AnimaMCM AnimaOptimizers ${ITKIO_LIBRARIES} diff --git a/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx b/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx index ac802c02b..34623f22d 100644 --- a/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx +++ b/Anima/diffusion/mcm_tools/mcm_merge_block_images/animaMCMMergeBlockImages.cxx @@ -3,14 +3,14 @@ #include #include -#include +#include #include #include int main(int argc, char **argv) { typedef anima::MCMImage ImageType; - typedef anima::MCMPrivateFileReader ImageReaderType; + typedef anima::MCMFileReader ImageReaderType; typedef anima::MCMFileWriter ImageWriterType; TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); diff --git a/Anima/diffusion/mcm_tools/mt_estimation_validation/CMakeLists.txt b/Anima/diffusion/mcm_tools/mt_estimation_validation/CMakeLists.txt index 1660d1d71..503fd86f0 100644 --- a/Anima/diffusion/mcm_tools/mt_estimation_validation/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/mt_estimation_validation/CMakeLists.txt @@ -21,8 +21,6 @@ add_executable(${PROJECT_NAME} ## ############################################################################# target_link_libraries(${PROJECT_NAME} - AnimaMCMPrivate - AnimaMCMPrivateBase AnimaMCM AnimaMCMBase AnimaOptimizers diff --git a/Anima/diffusion/mcm_tools/mt_estimation_validation/animaMTEstimationValidation.cxx b/Anima/diffusion/mcm_tools/mt_estimation_validation/animaMTEstimationValidation.cxx index 3712a12f6..60deaf688 100644 --- a/Anima/diffusion/mcm_tools/mt_estimation_validation/animaMTEstimationValidation.cxx +++ b/Anima/diffusion/mcm_tools/mt_estimation_validation/animaMTEstimationValidation.cxx @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -11,7 +11,7 @@ int main(int argc, char **argv) { typedef anima::MCMImage ImageType; typedef ImageType::MCMPointer MCMPointer; - typedef anima::MCMPrivateFileReader ImageReaderType; + typedef anima::MCMFileReader ImageReaderType; TCLAP::CmdLine cmd("Computes various squared distances between estimated and reference multi-tensors models.\n\ Assumes same number of compartments at each voxel.\n INRIA / IRISA - VisAGeS/Empenn Team", diff --git a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt index c8e86055c..12272d687 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/CMakeLists.txt @@ -24,7 +24,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} - AnimaMCMPrivate AnimaMCM AnimaMCMBase AnimaOptimizers diff --git a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx index 3f6930629..8657279e3 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx +++ b/Anima/diffusion/mcm_tools/test_averaging/create_grid_from_pixels/animaCreateGridFromPixels.cxx @@ -3,7 +3,7 @@ #include #include -#include +#include #include using namespace anima; @@ -85,7 +85,7 @@ int main(int argc, char *argv[]) size[1] = nbPoints1; size[2] = 1; - anima::PrivateMultiCompartmentModelCreator mcmCreator; + anima::MultiCompartmentModelCreator mcmCreator; mcmCreator.SetCompartmentType(anima::DDI); mcmCreator.SetNumberOfCompartments(numInput); mcmCreator.SetModelWithFreeWaterComponent(false); diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt index 28a5b29e1..2e24cf557 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/CMakeLists.txt @@ -24,7 +24,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} - AnimaMCMPrivate AnimaMCM AnimaMCMBase AnimaOptimizers diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx index 83ced300a..470244c73 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_averaging_on_a_grid/animaDDIAveragingOnAGrid.cxx @@ -5,7 +5,7 @@ #include #include -#include +#include int main(int argc, char *argv[]) { @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) size[1] = nbPoints1; size[2] = 1; - anima::PrivateMultiCompartmentModelCreator mcmCreator; + anima::MultiCompartmentModelCreator mcmCreator; mcmCreator.SetCompartmentType(anima::DDI); mcmCreator.SetNumberOfCompartments(1); mcmCreator.SetModelWithFreeWaterComponent(false); diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt index 54c9167c0..a5b9b070e 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/CMakeLists.txt @@ -23,8 +23,6 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} ${TinyXML2_LIBRARY} - AnimaMCMPrivate - AnimaMCMPrivateBase AnimaMCM AnimaMCMBase AnimaOptimizers diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx index 518bfa353..d8cc4de4a 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValue.cxx @@ -1,10 +1,10 @@ #include -#include +#include #include #include -#include +#include int main(int argc, char **argv) { @@ -29,7 +29,7 @@ int main(int argc, char **argv) anima::DDITestAveragingOnRealValueImageFilter::Pointer mainFilter = anima::DDITestAveragingOnRealValueImageFilter::New(); typedef anima::MCMImage ImageType; - typedef anima::MCMPrivateFileReader MCMReaderType; + typedef anima::MCMFileReader MCMReaderType; typedef anima::MCMFileWriter MCMWriterType; MCMReaderType mcmReader; @@ -43,7 +43,7 @@ int main(int argc, char **argv) mainFilter->SetMethod(methodArg.getValue()); mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); - anima::PrivateMultiCompartmentModelCreator mcmCreator; + anima::MultiCompartmentModelCreator mcmCreator; mcmCreator.SetModelWithFreeWaterComponent(false); mcmCreator.SetModelWithRestrictedWaterComponent(false); mcmCreator.SetModelWithStaniszComponent(false); diff --git a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx index 4efb07553..e20898b4f 100644 --- a/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx +++ b/Anima/diffusion/mcm_tools/test_averaging/ddi_test_averaging_on_real_value/animaDDITestAveragingOnRealValueImageFilter.cxx @@ -4,7 +4,7 @@ #include #include -#include +#include namespace anima { @@ -51,8 +51,8 @@ void DDITestAveragingOnRealValueImageFilter::DynamicThreadedGenerateData(const I InputImageType::SizeType totalSize = region.GetSize(); - typedef anima::MCMPrivateWeightedAverager::Pointer MCMAveragerPointer; - MCMAveragerPointer mcmAverager = anima::MCMPrivateWeightedAverager::New(); + typedef anima::MCMWeightedAverager::Pointer MCMAveragerPointer; + MCMAveragerPointer mcmAverager = anima::MCMWeightedAverager::New(); mcmAverager->SetDDIInterpolationMethod(m_Method); mcmAverager->SetOutputModel(m_ReferenceOutputModel); diff --git a/Anima/diffusion/tractography/CMakeLists.txt b/Anima/diffusion/tractography/CMakeLists.txt index c094864db..89db5f87a 100644 --- a/Anima/diffusion/tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/CMakeLists.txt @@ -29,6 +29,8 @@ target_link_libraries(${PROJECT_NAME} AnimaDataIO AnimaOptimizers AnimaSHTools + AnimaMCM + AnimaMCMBase ) ################################################################################ diff --git a/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt b/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt index 6174c0db2..a7d4dc978 100644 --- a/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/mcm_probabilistic_tractography/CMakeLists.txt @@ -26,9 +26,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} AnimaDataIO - AnimaPrivateTractography AnimaTractography - AnimaMCM AnimaOptimizers ${TinyXML2_LIBRARY} ${VTK_PREFIX}CommonCore diff --git a/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt b/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt index 2aacd5145..28003c1ca 100644 --- a/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt +++ b/Anima/diffusion/tractography/mcm_tractography/CMakeLists.txt @@ -26,10 +26,7 @@ add_executable(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} ${ITKIO_LIBRARIES} AnimaDataIO - AnimaPrivateTractography AnimaTractography - AnimaMCMPrivate - AnimaMCM AnimaOptimizers ${VTK_PREFIX}CommonCore ${VTKSYS_LIBRARY} diff --git a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx index e09238adb..de5d618f6 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx +++ b/Anima/math-tools/multi_compartment_base/animaMCMFileReader.hxx @@ -11,6 +11,7 @@ #include #include #include +#include #include #include diff --git a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx index efa3cff96..74187e909 100644 --- a/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx +++ b/Anima/math-tools/multi_compartment_base/animaMCMWeightedAverager.cxx @@ -1,4 +1,5 @@ #include +#include namespace anima { @@ -32,7 +33,7 @@ void MCMWeightedAverager::SetNumberOfOutputDirectionalCompartments(unsigned int } -void MCMDDIWeightedAverager::SetDDIInterpolationMethod(unsigned int method) +void MCMWeightedAverager::SetDDIInterpolationMethod(unsigned int method) { if (m_DDIInterpolationMethod == method) return; From 117592f94849def0d6958bbc353f4d3b5d31c7aa Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 12:29:14 +0200 Subject: [PATCH 06/17] finish fusion of math-tools. fusion of filtering --- Anima/filtering/CMakeLists.txt | 1 + .../kmeans_standardization/CMakeLists.txt | 37 ++ .../animaKMeansStandardization.cxx | 182 +++++++++ Anima/math-tools/arithmetic/CMakeLists.txt | 2 + .../average_linear_transforms/CMakeLists.txt | 36 ++ .../animaAverageLinearTransforms.cxx | 108 +++++ .../covariance_images/CMakeLists.txt | 36 ++ .../animaCovarianceImages.cxx | 368 ++++++++++++++++++ Anima/math-tools/common_tools/CMakeLists.txt | 1 + .../common_tools/enlarge_image/CMakeLists.txt | 36 ++ .../enlarge_image/animaEnlargeImage.cxx | 169 ++++++++ .../statistical_tests/CMakeLists.txt | 4 + .../animaCramersTestImageFilter.h | 115 ++++++ .../animaCramersTestImageFilter.hxx | 348 +++++++++++++++++ .../cramers_test/CMakeLists.txt | 36 ++ .../cramers_test/animaCramersTest.cxx | 179 +++++++++ Anima/math-tools/statistics/CMakeLists.txt | 1 + .../animaBootstrap4DVolumeImageFilter.h | 57 +++ .../animaBootstrap4DVolumeImageFilter.hxx | 94 +++++ .../boot_strap_4d_volume/CMakeLists.txt | 36 ++ .../animaBootstrap4DVolume.cxx | 100 +++++ 21 files changed, 1946 insertions(+) create mode 100644 Anima/filtering/kmeans_standardization/CMakeLists.txt create mode 100644 Anima/filtering/kmeans_standardization/animaKMeansStandardization.cxx create mode 100644 Anima/math-tools/arithmetic/average_linear_transforms/CMakeLists.txt create mode 100644 Anima/math-tools/arithmetic/average_linear_transforms/animaAverageLinearTransforms.cxx create mode 100644 Anima/math-tools/arithmetic/covariance_images/CMakeLists.txt create mode 100644 Anima/math-tools/arithmetic/covariance_images/animaCovarianceImages.cxx create mode 100644 Anima/math-tools/common_tools/enlarge_image/CMakeLists.txt create mode 100644 Anima/math-tools/common_tools/enlarge_image/animaEnlargeImage.cxx create mode 100644 Anima/math-tools/statistical_tests/animaCramersTestImageFilter.h create mode 100644 Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx create mode 100644 Anima/math-tools/statistical_tests/cramers_test/CMakeLists.txt create mode 100644 Anima/math-tools/statistical_tests/cramers_test/animaCramersTest.cxx create mode 100644 Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.h create mode 100644 Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.hxx create mode 100644 Anima/math-tools/statistics/boot_strap_4d_volume/CMakeLists.txt create mode 100644 Anima/math-tools/statistics/boot_strap_4d_volume/animaBootstrap4DVolume.cxx diff --git a/Anima/filtering/CMakeLists.txt b/Anima/filtering/CMakeLists.txt index 9586ff701..f20d1ed1b 100644 --- a/Anima/filtering/CMakeLists.txt +++ b/Anima/filtering/CMakeLists.txt @@ -7,6 +7,7 @@ project(ANIMA-FILTERING) add_subdirectory(bias_correction) add_subdirectory(denoising) add_subdirectory(dti_tools) +add_subdirectory(kmeans_standardization) add_subdirectory(noise_generator) add_subdirectory(nyul_standardization) add_subdirectory(regularization) diff --git a/Anima/filtering/kmeans_standardization/CMakeLists.txt b/Anima/filtering/kmeans_standardization/CMakeLists.txt new file mode 100644 index 000000000..59fcf8154 --- /dev/null +++ b/Anima/filtering/kmeans_standardization/CMakeLists.txt @@ -0,0 +1,37 @@ +if(BUILD_TOOLS) + +project(animaKMeansStandardization) + +## ############################################################################# +## 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} + ITKStatistics + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/filtering/kmeans_standardization/animaKMeansStandardization.cxx b/Anima/filtering/kmeans_standardization/animaKMeansStandardization.cxx new file mode 100644 index 000000000..667ea382e --- /dev/null +++ b/Anima/filtering/kmeans_standardization/animaKMeansStandardization.cxx @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include + +void PerformLinearRegression(const std::vector &x, const std::vector &y, + double &slope, double &intercept) +{ + double correlation = 0.0; + double meanX = 0.0; + double meanY = 0.0; + double varianceX = 0.0; + unsigned int sizeOfArray = x.size(); + + for (unsigned int arrayIndex = 0;arrayIndex < sizeOfArray;++arrayIndex) + { + meanX += x[arrayIndex]; + meanY += y[arrayIndex]; + } + + meanX /= sizeOfArray; + meanY /= sizeOfArray; + + for (unsigned int arrayIndex = 0;arrayIndex < sizeOfArray;++arrayIndex) + { + correlation += (x[arrayIndex] - meanX) * (y[arrayIndex] - meanY); + varianceX += (x[arrayIndex] - meanX) * (x[arrayIndex] - meanX); + } + + slope = correlation / varianceX; + intercept = meanY - slope * meanX; +} + +template +void GetKMeansClassesMeans(itk::Image *image, itk::Image *mask, + std::vector &imageClassMeans) +{ + unsigned int numberOfClasses = 3; + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + itk::ImageRegionIterator imageItr(image, image->GetLargestPossibleRegion()); + itk::ImageRegionIterator maskItr(mask, mask->GetLargestPossibleRegion()); + + unsigned int numPixels = 0; + double imageMeanInMask = 0; + while (!imageItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + imageMeanInMask += imageItr.Get(); + ++numPixels; + } + + ++imageItr; + ++maskItr; + } + + imageMeanInMask /= numPixels; + + typedef itk::Statistics::MeasurementVectorPixelTraits ::MeasurementVectorType MeasurementVectorType; + typedef itk::Statistics::ListSample ListSampleType; + typedef itk::Statistics::WeightedCentroidKdTreeGenerator TreeGeneratorType; + + ListSampleType::Pointer treeData = ListSampleType::New(); + treeData->Resize(numPixels); + + maskItr.GoToBegin(); + imageItr.GoToBegin(); + unsigned int pos = 0; + while (!imageItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + treeData->SetMeasurementVector(pos, MeasurementVectorType(imageItr.Get())); + ++pos; + } + + ++imageItr; + ++maskItr; + } + + typedef TreeGeneratorType::KdTreeType TreeType; + typedef itk::Statistics::KdTreeBasedKmeansEstimator EstimatorType; + typedef EstimatorType::ParametersType ParametersType; + + TreeGeneratorType::Pointer treeGenerator = TreeGeneratorType::New(); + treeGenerator->SetSample(treeData); + treeGenerator->SetBucketSize(16); + treeGenerator->Update(); + + ParametersType initialMeans(numberOfClasses); + initialMeans[0] = 0.3 * imageMeanInMask; + initialMeans[1] = 0.7 * imageMeanInMask; + initialMeans[2] = 0.9 * imageMeanInMask; + + EstimatorType::Pointer estimator = EstimatorType::New(); + estimator->SetParameters(initialMeans); + + estimator->SetKdTree(treeGenerator->GetOutput()); + estimator->SetMaximumIteration(200); + estimator->SetCentroidPositionChangesThreshold(0.0); + estimator->StartOptimization(); + + ParametersType finalMeans = estimator->GetParameters(); + + imageClassMeans.resize(numberOfClasses); + for (unsigned int i = 0;i < numberOfClasses;++i) + imageClassMeans[i] = finalMeans[i]; +} + +int main(int argc, char* argv[]) +{ + TCLAP::CmdLine cmd("Perform longitudinal intensity normalization. For more details on the method, see http://www.hal.inserm.fr/inserm-01074699/document", ' ', ANIMA_VERSION); + + TCLAP::ValueArg referenceImageArg("r","ref","Reference image for intensity .",true,"","Reference image.",cmd); + TCLAP::ValueArg refMaskArg("R","ref-mask","Reference mask for intensity normalization.",true,"","Reference mask",cmd); + TCLAP::ValueArg movingImageArg("m","mov","Moving image for intensity normalization.",true,"","Moving image.", cmd); + TCLAP::ValueArg movingMaskArg("M","mov-mask","Moving mask for intensity normalization.",false,"","moving mask",cmd); + TCLAP::ValueArg outputArg("o", "output","Normalised output image",true,"","output image",cmd); + + TCLAP::ValueArg numThreadsArg("T","num-threads","Number of cores to run on (default: all cores)",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"Number of cores",cmd); + + try + { + cmd.parse(argc, argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + typedef itk::Image ImageType; + typedef itk::Image MaskType; + + ImageType::Pointer baselineImage = anima::readImage(referenceImageArg.getValue()); + ImageType::Pointer repeatImage = anima::readImage(movingImageArg.getValue()); + MaskType::Pointer refMask = anima::readImage(refMaskArg.getValue()); + + MaskType::Pointer movingMask; + if (movingMaskArg.getValue() != "") + movingMask = anima::readImage(movingMaskArg.getValue()); + else + movingMask = anima::readImage(refMaskArg.getValue()); + + // Calculate the class means for each image + unsigned int numberOfClasses = 3; + std::vector baselineFinalMeans(numberOfClasses); + GetKMeansClassesMeans (baselineImage, refMask, baselineFinalMeans); + + std::vector repeatFinalMeans(numberOfClasses); + GetKMeansClassesMeans (repeatImage, movingMask, repeatFinalMeans); + + itk::ImageRegionIterator repeatItr(repeatImage, repeatImage->GetLargestPossibleRegion()); + itk::ImageRegionIterator movingMaskItr(movingMask, movingMask->GetLargestPossibleRegion()); + + double slopeRepeatBaseline = 0.0; + double interceptRepeatBaseline = 0.0; + + // Repeat (x) regress on baseline (y). + PerformLinearRegression(repeatFinalMeans, baselineFinalMeans, slopeRepeatBaseline, + interceptRepeatBaseline); + + movingMaskItr.GoToBegin(); + for (repeatItr.GoToBegin(); !repeatItr.IsAtEnd(); ++repeatItr) + { + if (movingMaskItr.Get() != 0) + { + double repeatValue = repeatItr.Get(); + repeatItr.Set(std::abs(slopeRepeatBaseline * repeatValue + interceptRepeatBaseline)); + } + + ++movingMaskItr; + } + + anima::writeImage(outputArg.getValue(), repeatImage); + + return EXIT_SUCCESS; +} diff --git a/Anima/math-tools/arithmetic/CMakeLists.txt b/Anima/math-tools/arithmetic/CMakeLists.txt index cfed80471..002d14cd2 100644 --- a/Anima/math-tools/arithmetic/CMakeLists.txt +++ b/Anima/math-tools/arithmetic/CMakeLists.txt @@ -4,6 +4,8 @@ if (USE_RPI AND RPI_FOUND) add_subdirectory(dense_transform_arithmetic) endif() +add_subdirectory(average_linear_transforms) +add_subdirectory(covariance_images) add_subdirectory(image_arithmetic) add_subdirectory(linear_transform_to_svf) add_subdirectory(linear_transform_arithmetic) diff --git a/Anima/math-tools/arithmetic/average_linear_transforms/CMakeLists.txt b/Anima/math-tools/arithmetic/average_linear_transforms/CMakeLists.txt new file mode 100644 index 000000000..5949b705b --- /dev/null +++ b/Anima/math-tools/arithmetic/average_linear_transforms/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaAverageLinearTransforms) + +## ############################################################################# +## 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} + ${ITK_TRANSFORM_LIBRARIES} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/arithmetic/average_linear_transforms/animaAverageLinearTransforms.cxx b/Anima/math-tools/arithmetic/average_linear_transforms/animaAverageLinearTransforms.cxx new file mode 100644 index 000000000..277084a8f --- /dev/null +++ b/Anima/math-tools/arithmetic/average_linear_transforms/animaAverageLinearTransforms.cxx @@ -0,0 +1,108 @@ +#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","Input transforms list in text file",true,"","input transforms list",cmd); + TCLAP::ValueArg outArg("o","output","Output transform",true,"","output transform",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef double PrecisionType; + const unsigned int Dimension = 3; + typedef itk::AffineTransform MatrixTransformType; + typedef MatrixTransformType::Pointer MatrixTransformPointer; + typedef vnl_matrix MatrixType; + + unsigned int nbTrsfs = 0; + char trsfN[2048]; + std::ifstream trsfIn(inArg.getValue().c_str()); + + MatrixType workMatrix(Dimension+1,Dimension+1), logWorkMatrix(Dimension+1,Dimension+1); + logWorkMatrix.fill(0); + + while(!trsfIn.eof()) + { + trsfIn.getline(trsfN,2048); + + if (strcmp(trsfN,"") == 0) + continue; + + itk::TransformFileReader::Pointer trReader = itk::TransformFileReader::New(); + trReader->SetFileName(trsfN); + + try + { + trReader->Update(); + } + catch (itk::ExceptionObject &e) + { + std::cerr << "Problem reading transform file " << trsfN << ", exiting" << std::endl; + return 1; + } + + itk::TransformFileReader::TransformListType trsfList = *(trReader->GetTransformList()); + itk::TransformFileReader::TransformListType::iterator tr_it = trsfList.begin(); + + MatrixTransformType *trsf = dynamic_cast ((*tr_it).GetPointer()); + + if (trsf == NULL) + { + std::cerr << "Problem converting transform file to linear file " << trsfN << ", exiting" << std::endl; + return 1; + } + + workMatrix.set_identity(); + for (unsigned int i = 0;i < Dimension;++i) + { + workMatrix(i,Dimension) = trsf->GetOffset()[i]; + for (unsigned int j = 0;j < Dimension;++j) + workMatrix(i,j) = trsf->GetMatrix()(i,j); + } + + logWorkMatrix += anima::GetLogarithm(workMatrix); + ++nbTrsfs; + } + + logWorkMatrix /= nbTrsfs; + + workMatrix = anima::GetExponential(logWorkMatrix); + + MatrixTransformPointer outTrsf = MatrixTransformType::New(); + outTrsf->SetIdentity(); + + MatrixTransformType::OffsetType outOffset; + MatrixTransformType::MatrixType outMatrix; + + for (unsigned int i = 0;i < Dimension;++i) + { + outOffset[i] = workMatrix(i,Dimension); + for (unsigned int j = 0;j < Dimension;++j) + outMatrix(i,j) = workMatrix(i,j); + } + + outTrsf->SetMatrix(outMatrix); + outTrsf->SetOffset(outOffset); + + itk::TransformFileWriter::Pointer trWriter = itk::TransformFileWriter::New(); + trWriter->SetInput(outTrsf); + trWriter->SetFileName(outArg.getValue()); + + trWriter->Update(); + + return 0; +} diff --git a/Anima/math-tools/arithmetic/covariance_images/CMakeLists.txt b/Anima/math-tools/arithmetic/covariance_images/CMakeLists.txt new file mode 100644 index 000000000..567cae169 --- /dev/null +++ b/Anima/math-tools/arithmetic/covariance_images/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaCovarianceImages) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/arithmetic/covariance_images/animaCovarianceImages.cxx b/Anima/math-tools/arithmetic/covariance_images/animaCovarianceImages.cxx new file mode 100644 index 000000000..57fa8f183 --- /dev/null +++ b/Anima/math-tools/arithmetic/covariance_images/animaCovarianceImages.cxx @@ -0,0 +1,368 @@ +#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","inputfiles","Input image list in text file",true,"","input image list",cmd); + TCLAP::ValueArg outArg("o","outputfile","Output covariance image",true,"","output image",cmd); + TCLAP::ValueArg maskArg("m","maskfiles","Input masks list in text file",false,"","input masks list",cmd); + TCLAP::SwitchArg stdevArg("S","output-stdev", "If set, output the square root instead of covariance matrix", cmd, false); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::VectorImage VectorImageType; + typedef itk::ImageFileReader itkVectorReader; + + typedef itk::Image UShortImageType; + typedef itk::ImageFileReader itkUShortReader; + + typedef itk::VectorImage OutputImageType; + typedef itk::ImageFileWriter OutputWriterType; + + std::ifstream masksIn; + if (maskArg.getValue() != "") + masksIn.open(maskArg.getValue().c_str()); + + unsigned int nbImages = 0; + char refN[2048], maskN[2048]; + + OutputImageType::Pointer outputData; + VectorImageType::Pointer meanData, squaredSumData; + UShortImageType::Pointer tmpSumMasks; + + std::ifstream imageIn(inArg.getValue().c_str()); + unsigned int vectorSize = 0; + unsigned int covVectorSize = 0; + + while (meanData.IsNull()) + { + imageIn.getline(refN,2048); + + if (strcmp(refN,"") == 0) + { + if (masksIn.is_open()) + masksIn.getline(maskN,2048); + continue; + } + + std::cout << "Processing image " << refN << "..." << std::endl; + std::string fileN(refN); + + itkVectorReader::Pointer tmpRead = itkVectorReader::New(); + tmpRead->SetFileName(refN); + tmpRead->Update(); + meanData = tmpRead->GetOutput(); + + squaredSumData = VectorImageType::New(); + squaredSumData->Initialize(); + squaredSumData->SetOrigin(meanData->GetOrigin()); + squaredSumData->SetSpacing(meanData->GetSpacing()); + squaredSumData->SetDirection(meanData->GetDirection()); + squaredSumData->SetRegions(meanData->GetLargestPossibleRegion()); + + vectorSize = meanData->GetNumberOfComponentsPerPixel(); + covVectorSize = vectorSize * (vectorSize + 1) / 2; + squaredSumData->SetNumberOfComponentsPerPixel(covVectorSize); + squaredSumData->Allocate(); + + itk::ImageRegionIterator tmpIt(tmpRead->GetOutput(),tmpRead->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator sSumIt(squaredSumData,tmpRead->GetOutput()->GetLargestPossibleRegion()); + + VectorImageType::PixelType tmpVec(vectorSize), sSumVec(covVectorSize); + + if (masksIn.is_open()) + { + masksIn.getline(maskN,2048); + itkUShortReader::Pointer tmpMaskRead = itkUShortReader::New(); + tmpMaskRead->SetFileName(maskN); + tmpMaskRead->Update(); + + tmpSumMasks = tmpMaskRead->GetOutput(); + + itk::ImageRegionIterator tmpMaskIt(tmpMaskRead->GetOutput(),tmpRead->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator meanIt(meanData,meanData->GetLargestPossibleRegion()); + + while (!tmpMaskIt.IsAtEnd()) + { + if (tmpMaskIt.Get() == 0) + { + sSumVec.Fill(0); + sSumIt.Set(sSumVec); + tmpVec.Fill(0); + meanIt.Set(tmpVec); + ++tmpMaskIt; + ++tmpIt; + ++sSumIt; + ++meanIt; + continue; + } + + sSumVec = sSumIt.Get(); + tmpVec = tmpIt.Get(); + + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + sSumVec[pos] = tmpVec[i] * tmpVec[j]; + ++pos; + } + + sSumIt.Set(sSumVec); + + ++tmpMaskIt; + ++tmpIt; + ++sSumIt; + ++meanIt; + } + } + else + { + while (!tmpIt.IsAtEnd()) + { + sSumVec = sSumIt.Get(); + tmpVec = tmpIt.Get(); + + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + sSumVec[pos] = tmpVec[i] * tmpVec[j]; + ++pos; + } + + sSumIt.Set(sSumVec); + + ++tmpIt; + ++sSumIt; + } + } + + nbImages++; + } + + while(!imageIn.eof()) + { + imageIn.getline(refN,2048); + + if (strcmp(refN,"") == 0) + { + if (masksIn.is_open()) + masksIn.getline(maskN,2048); + continue; + } + + std::cout << "Processing image " << refN << "..." << std::endl; + std::string fileN(refN); + + itkVectorReader::Pointer tmpRead = itkVectorReader::New(); + tmpRead->SetFileName(refN); + tmpRead->Update(); + + itk::ImageRegionIterator tmpIt(tmpRead->GetOutput(),tmpRead->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator meanIt(meanData,tmpRead->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator sSumIt(squaredSumData,tmpRead->GetOutput()->GetLargestPossibleRegion()); + + VectorImageType::PixelType tmpVec, sSumVec, meanVec; + + if (masksIn.is_open()) + { + masksIn.getline(maskN,2048); + itkUShortReader::Pointer tmpMaskRead = itkUShortReader::New(); + tmpMaskRead->SetFileName(maskN); + tmpMaskRead->Update(); + + itk::ImageRegionIterator tmpMaskIt(tmpMaskRead->GetOutput(),tmpRead->GetOutput()->GetLargestPossibleRegion()); + itk::ImageRegionIterator sumMasksIt(tmpSumMasks,tmpRead->GetOutput()->GetLargestPossibleRegion()); + + while (!sumMasksIt.IsAtEnd()) + { + if (tmpMaskIt.Get() == 0) + { + ++tmpMaskIt; + ++sumMasksIt; + ++tmpIt; + ++meanIt; + ++sSumIt; + continue; + } + + sumMasksIt.Set(sumMasksIt.Get() + 1); + + sSumVec = sSumIt.Get(); + tmpVec = tmpIt.Get(); + meanVec = meanIt.Get(); + + meanIt.Set(meanVec + tmpVec); + + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + sSumVec[pos] += tmpVec[i] * tmpVec[j]; + ++pos; + } + + sSumIt.Set(sSumVec); + + ++tmpMaskIt; + ++sumMasksIt; + ++tmpIt; + ++meanIt; + ++sSumIt; + } + } + else + { + while (!tmpIt.IsAtEnd()) + { + sSumVec = sSumIt.Get(); + tmpVec = tmpIt.Get(); + meanVec = meanIt.Get(); + + meanIt.Set(meanVec + tmpVec); + + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + sSumVec[pos] += tmpVec[i] * tmpVec[j]; + ++pos; + } + + sSumIt.Set(sSumVec); + + ++tmpIt; + ++meanIt; + ++sSumIt; + } + } + + nbImages++; + } + + if (masksIn.is_open()) + masksIn.close(); + + imageIn.close(); + + std::cout << "Compute covariance now..." << std::endl; + + // Now compute output + + outputData = OutputImageType::New(); + outputData->Initialize(); + outputData->SetOrigin(meanData->GetOrigin()); + outputData->SetSpacing(meanData->GetSpacing()); + outputData->SetDirection(meanData->GetDirection()); + outputData->SetRegions(meanData->GetLargestPossibleRegion()); + outputData->SetNumberOfComponentsPerPixel(covVectorSize); + + outputData->Allocate(); + + anima::LogEuclideanTensorCalculator ::Pointer leCalculator = anima::LogEuclideanTensorCalculator ::New(); + + if (!tmpSumMasks.IsNull()) + { + itk::ImageRegionIterator resIt(outputData,outputData->GetLargestPossibleRegion()); + itk::ImageRegionIterator sumMasksIt(tmpSumMasks,outputData->GetLargestPossibleRegion()); + itk::ImageRegionIterator meanIt(meanData,outputData->GetLargestPossibleRegion()); + itk::ImageRegionIterator sSumIt(squaredSumData,outputData->GetLargestPossibleRegion()); + + VectorImageType::PixelType meanVec, sSumVec, resVec(covVectorSize); + vnl_matrix tmpCovMatrix(vectorSize,vectorSize); + vnl_matrix tmpStDevMatrix(vectorSize,vectorSize); + + while (!resIt.IsAtEnd()) + { + resVec.Fill(0); + if (sumMasksIt.Get() > 1) + { + meanVec = meanIt.Get(); + sSumVec = sSumIt.Get(); + unsigned int numVals = sumMasksIt.Get(); + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + resVec[pos] = (sSumVec[pos] - meanVec[i] * meanVec[j] / numVals) / (numVals - 1.0); + ++pos; + } + + if (stdevArg.isSet()) + { + anima::GetTensorFromVectorRepresentation(resVec,tmpCovMatrix); + leCalculator->GetTensorPower(tmpCovMatrix,tmpStDevMatrix,0.5); + anima::GetVectorRepresentation(tmpStDevMatrix,resVec); + } + } + + resIt.Set(resVec); + + ++resIt; + ++meanIt; + ++sSumIt; + ++sumMasksIt; + } + } + else + { + itk::ImageRegionIterator resIt(outputData,outputData->GetLargestPossibleRegion()); + itk::ImageRegionIterator meanIt(meanData,outputData->GetLargestPossibleRegion()); + itk::ImageRegionIterator sSumIt(squaredSumData,outputData->GetLargestPossibleRegion()); + VectorImageType::PixelType meanVec, sSumVec, resVec(covVectorSize); + vnl_matrix tmpCovMatrix(vectorSize,vectorSize); + vnl_matrix tmpStDevMatrix(vectorSize,vectorSize); + + while (!resIt.IsAtEnd()) + { + meanVec = meanIt.Get(); + sSumVec = sSumIt.Get(); + + unsigned int pos = 0; + for (unsigned int i = 0;i < vectorSize;++i) + for (unsigned int j = i;j < vectorSize;++j) + { + resVec[pos] = (sSumVec[pos] - meanVec[i] * meanVec[j] / nbImages) / (nbImages - 1.0); + ++pos; + } + + if (stdevArg.isSet()) + { + anima::GetTensorFromVectorRepresentation(resVec,tmpCovMatrix); + leCalculator->GetTensorPower(tmpCovMatrix,tmpStDevMatrix,0.5); + anima::GetVectorRepresentation(tmpStDevMatrix,resVec); + } + + resIt.Set(resVec); + + ++resIt; + ++meanIt; + ++sSumIt; + } + } + + OutputWriterType::Pointer tmpWrite = OutputWriterType::New(); + tmpWrite->SetInput(outputData); + tmpWrite->SetFileName(outArg.getValue()); + tmpWrite->SetUseCompression(true); + + tmpWrite->Update(); + + return 0; +} diff --git a/Anima/math-tools/common_tools/CMakeLists.txt b/Anima/math-tools/common_tools/CMakeLists.txt index 18f38ddeb..4a0a3b040 100644 --- a/Anima/math-tools/common_tools/CMakeLists.txt +++ b/Anima/math-tools/common_tools/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(concatenate_images) add_subdirectory(create_image) add_subdirectory(crop_image) +add_subdirectory(enlarge_image) add_subdirectory(collapse_image) add_subdirectory(convert_image) add_subdirectory(convert_shape) diff --git a/Anima/math-tools/common_tools/enlarge_image/CMakeLists.txt b/Anima/math-tools/common_tools/enlarge_image/CMakeLists.txt new file mode 100644 index 000000000..9ec8eefd5 --- /dev/null +++ b/Anima/math-tools/common_tools/enlarge_image/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaEnlargeImage) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/common_tools/enlarge_image/animaEnlargeImage.cxx b/Anima/math-tools/common_tools/enlarge_image/animaEnlargeImage.cxx new file mode 100644 index 000000000..d41108760 --- /dev/null +++ b/Anima/math-tools/common_tools/enlarge_image/animaEnlargeImage.cxx @@ -0,0 +1,169 @@ +#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","input image",true,"","input image",cmd); + TCLAP::ValueArg outArg("o","outputfile","output image",true,"","output image",cmd); + TCLAP::ValueArg xdimArg("x","xdim","Add N on each side of the image in the X direction (default: 0)",false,0,"Xdim add",cmd); + TCLAP::ValueArg ydimArg("y","ydim","Add N on each side of the image in the Y direction (default: 0)",false,0,"Ydim add",cmd); + TCLAP::ValueArg zdimArg("z","zdim","Add N on each side of the image in the Z direction (default: 0)",false,0,"Zdim add",cmd); + TCLAP::ValueArg tdimArg("t","tdim","Add N on each side of the image in the Z direction (default: 0)",false,0,"Tdim add",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image ImageType; + typedef itk::Image Image4DType; + typedef itk::VectorImage VectorImageType; + + itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(inArg.getValue().c_str(), + itk::IOFileModeEnum::ReadMode); + + imageIO->SetFileName(inArg.getValue()); + imageIO->ReadImageInformation(); + + bool vectorImage = (imageIO->GetNumberOfComponents() > 1); + bool fourDimensionalImage = (imageIO->GetNumberOfDimensions() == 4); + + if (vectorImage) + { + // Vectorial case + VectorImageType::Pointer image = anima::readImage (inArg.getValue()); + + VectorImageType::Pointer outImage = VectorImageType::New(); + outImage->Initialize(); + outImage->SetNumberOfComponentsPerPixel(image->GetNumberOfComponentsPerPixel()); + outImage->SetDirection(image->GetDirection()); + outImage->SetSpacing(image->GetSpacing()); + outImage->SetOrigin(image->GetOrigin()); + + VectorImageType::RegionType largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetSize(0,largestRegion.GetSize()[0] + 2 * xdimArg.getValue()); + largestRegion.SetSize(1,largestRegion.GetSize()[1] + 2 * ydimArg.getValue()); + largestRegion.SetSize(2,largestRegion.GetSize()[2] + 2 * zdimArg.getValue()); + + outImage->SetRegions(largestRegion); + outImage->Allocate(); + + VectorImageType::PixelType zeroVec(image->GetNumberOfComponentsPerPixel()); + zeroVec.Fill(0); + outImage->FillBuffer(zeroVec); + + itk::ImageRegionIterator inItr(image,image->GetLargestPossibleRegion()); + + largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetIndex(0,largestRegion.GetIndex()[0] + xdimArg.getValue()); + largestRegion.SetIndex(1,largestRegion.GetIndex()[1] + ydimArg.getValue()); + largestRegion.SetIndex(2,largestRegion.GetIndex()[2] + zdimArg.getValue()); + itk::ImageRegionIterator outItr(outImage,largestRegion); + + while (!outItr.IsAtEnd()) + { + outItr.Set(inItr.Get()); + + ++inItr; + ++outItr; + } + + anima::writeImage (outArg.getValue(),outImage); + + return EXIT_SUCCESS; + } + else if (fourDimensionalImage) + { + // Vectorial case + Image4DType::Pointer image = anima::readImage (inArg.getValue()); + + Image4DType::Pointer outImage = Image4DType::New(); + outImage->Initialize(); + outImage->SetDirection(image->GetDirection()); + outImage->SetSpacing(image->GetSpacing()); + outImage->SetOrigin(image->GetOrigin()); + + Image4DType::RegionType largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetSize(0,largestRegion.GetSize()[0] + 2 * xdimArg.getValue()); + largestRegion.SetSize(1,largestRegion.GetSize()[1] + 2 * ydimArg.getValue()); + largestRegion.SetSize(2,largestRegion.GetSize()[2] + 2 * zdimArg.getValue()); + largestRegion.SetSize(3,largestRegion.GetSize()[3] + 2 * tdimArg.getValue()); + + outImage->SetRegions(largestRegion); + outImage->Allocate(); + outImage->FillBuffer(0.0); + + itk::ImageRegionIterator inItr(image,image->GetLargestPossibleRegion()); + + largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetIndex(0,largestRegion.GetIndex()[0] + xdimArg.getValue()); + largestRegion.SetIndex(1,largestRegion.GetIndex()[1] + ydimArg.getValue()); + largestRegion.SetIndex(2,largestRegion.GetIndex()[2] + zdimArg.getValue()); + largestRegion.SetIndex(3,largestRegion.GetIndex()[3] + tdimArg.getValue()); + + itk::ImageRegionIterator outItr(outImage,largestRegion); + + while (!outItr.IsAtEnd()) + { + outItr.Set(inItr.Get()); + + ++inItr; + ++outItr; + } + std::cout << "Writing Enlarge Image : " << outArg.getValue() << std::endl; + anima::writeImage (outArg.getValue(),outImage); + + return EXIT_SUCCESS; + } + + ImageType::Pointer image = anima::readImage (inArg.getValue()); + + ImageType::Pointer outImage = ImageType::New(); + outImage->Initialize(); + outImage->SetDirection(image->GetDirection()); + outImage->SetSpacing(image->GetSpacing()); + outImage->SetOrigin(image->GetOrigin()); + + ImageType::RegionType largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetSize(0,largestRegion.GetSize()[0] + 2 * xdimArg.getValue()); + largestRegion.SetSize(1,largestRegion.GetSize()[1] + 2 * ydimArg.getValue()); + largestRegion.SetSize(2,largestRegion.GetSize()[2] + 2 * zdimArg.getValue()); + + outImage->SetRegions(largestRegion); + outImage->Allocate(); + outImage->FillBuffer(0.0); + + itk::ImageRegionIterator inItr(image,image->GetLargestPossibleRegion()); + + largestRegion = image->GetLargestPossibleRegion(); + largestRegion.SetIndex(0,largestRegion.GetIndex()[0] + xdimArg.getValue()); + largestRegion.SetIndex(1,largestRegion.GetIndex()[1] + ydimArg.getValue()); + largestRegion.SetIndex(2,largestRegion.GetIndex()[2] + zdimArg.getValue()); + itk::ImageRegionIterator outItr(outImage,largestRegion); + + while (!outItr.IsAtEnd()) + { + outItr.Set(inItr.Get()); + + ++inItr; + ++outItr; + } + + anima::writeImage (outArg.getValue(),outImage); + + return EXIT_SUCCESS; +} diff --git a/Anima/math-tools/statistical_tests/CMakeLists.txt b/Anima/math-tools/statistical_tests/CMakeLists.txt index 3e3c4b9e7..a02d4e16f 100644 --- a/Anima/math-tools/statistical_tests/CMakeLists.txt +++ b/Anima/math-tools/statistical_tests/CMakeLists.txt @@ -52,3 +52,7 @@ add_subdirectory(low_memory_tools) add_subdirectory(nlmeans_patient_to_group_comparison) add_subdirectory(patient_to_group_odf_comparison) add_subdirectory(patient_to_group_comparison) + +if (BUILD_TESTING) + add_subdirectory(cramers_test) +endif() \ No newline at end of file diff --git a/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.h b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.h new file mode 100644 index 000000000..87c574b12 --- /dev/null +++ b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace anima +{ + +template +class CramersTestImageFilter : + public anima::MaskedImageToImageFilter< itk::VectorImage , itk::Image > +{ +public: + + /** Standard class typedefs. */ + typedef CramersTestImageFilter Self; + typedef itk::VectorImage TInputImage; + typedef itk::Image TOutputImage; + typedef anima::MaskedImageToImageFilter< 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(CramersTestImageFilter, MaskedImageToImageFilter) + + /** Image typedef support */ + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + typedef typename InputImageType::PixelType InputPixelType; + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::MaskImageType MaskImageType; + typedef typename MaskImageType::Pointer MaskImagePointer; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + /** Set the number of samples to build the distribution */ + itkSetMacro(NbSamples, unsigned long int) + itkGetMacro(NbSamples, unsigned long int) + + void SetFirstGroupIndexes(std::vector &group) + { + m_FirstGroup.clear(); + m_FirstGroupSize = group.size(); + + for (unsigned int i = 0;i < m_FirstGroupSize;++i) + m_FirstGroup.push_back(group[i]); + } + + void SetSecondGroupIndexes(std::vector &group) + { + m_SecondGroup.clear(); + m_SecondGroupSize = group.size(); + + for (unsigned int i = 0;i < m_SecondGroupSize;++i) + m_SecondGroup.push_back(group[i]); + } + + void AddOutlierMask(MaskImageType *mask) + { + m_OutlierMasks.push_back(mask); + } + +protected: + CramersTestImageFilter() + : Superclass() + { + m_NbSamples = 5000; + m_FirstGroup.clear(); + m_SecondGroup.clear(); + m_FirstGroupSize = 0; + m_SecondGroupSize = 0; + + m_UseOutlierMasks = false; + m_OutlierMasks.clear(); + } + + virtual ~CramersTestImageFilter() {} + + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + //! Bootstrap samples generator for group comparison + void GenerateBootStrapSamples(); + + //! Actually bootstraps a p-value from the computed distance matrix and group samples + double BootStrap(vnl_matrix &groupDistMatrix, std::vector &inlierWeights); + + //! Computes the Cramers' statistic knowing the distance matrix and the two groups + double CramerStatistic(vnl_matrix &grpDistMatrix, std::vector &inlierWeights, + unsigned int index); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(CramersTestImageFilter); + + std::vector < unsigned int > m_FirstGroup, m_SecondGroup; + std::vector < std::vector < unsigned int > > m_SamplesFirstGroup, m_SamplesSecondGroup; + + unsigned long int m_NbSamples; + unsigned int m_FirstGroupSize, m_SecondGroupSize; + + std::vector m_OutlierMasks; + bool m_UseOutlierMasks; +}; + +} // end namespace anima + +#include "animaCramersTestImageFilter.hxx" diff --git a/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx new file mode 100644 index 000000000..71d3175ae --- /dev/null +++ b/Anima/math-tools/statistical_tests/animaCramersTestImageFilter.hxx @@ -0,0 +1,348 @@ +#pragma once +#include "animaCramersTestImageFilter.h" + +#include +#include +#include +#include + +#include +#include + +#include + +namespace anima +{ + +template +void +CramersTestImageFilter +::BeforeThreadedGenerateData () +{ + Superclass::BeforeThreadedGenerateData(); + + this->GetOutput()->FillBuffer(0); + + // Checking consistency of the data and parameters + + unsigned int nbInputs = this->GetNumberOfIndexedInputs(); + if (nbInputs <= 1) + itkExceptionMacro("Error: Not enough inputs available... Exiting..."); + + if ((m_FirstGroupSize + m_SecondGroupSize) != nbInputs) + itkExceptionMacro("Groups data not clearly wrong... Exiting..."); + + m_SamplesFirstGroup.clear(); + m_SamplesSecondGroup.clear(); + + this->GenerateBootStrapSamples(); + + m_UseOutlierMasks = (m_OutlierMasks.size() == nbInputs); +} + +template +void +CramersTestImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + typedef itk::ImageRegionConstIterator < TInputImage > InIteratorType; + typedef itk::ImageRegionIterator < OutputImageType > OutRegionIteratorType; + + typedef itk::ImageRegionIterator < MaskImageType > MaskRegionIteratorType; + + OutRegionIteratorType outIterator(this->GetOutput(), outputRegionForThread); + MaskRegionIteratorType maskIterator (this->GetComputationMask(), outputRegionForThread); + + std::vector inIterators(this->GetNumberOfIndexedInputs()); + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + inIterators[i] = InIteratorType(this->GetInput(i), outputRegionForThread); + + std::vector outlierMasksIterators(m_OutlierMasks.size()); + + if (m_UseOutlierMasks) + { + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + outlierMasksIterators[i] = MaskRegionIteratorType(m_OutlierMasks[i],outputRegionForThread); + } + + std::vector < InputPixelType > firstGroupData(m_FirstGroupSize), secondGroupData(m_SecondGroupSize); + vnl_matrix cramerDistMatrix(this->GetNumberOfIndexedInputs(),this->GetNumberOfIndexedInputs(),0.0); + std::vector inlierProbabilities(this->GetNumberOfIndexedInputs(),1); + unsigned int vectorSize = this->GetInput(0)->GetNumberOfComponentsPerPixel(); + + while (!outIterator.IsAtEnd()) + { + if (maskIterator.Get() == 0) + { + outIterator.Set(0.0); + ++outIterator; + ++maskIterator; + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + ++inIterators[i]; + + if (m_UseOutlierMasks) + { + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + ++outlierMasksIterators[i]; + } + + continue; + } + + // Voxel is in mask, so now let's go for it and compute the stats + for (unsigned int i = 0; i < m_FirstGroupSize;++i) + { + unsigned int indFirst = m_FirstGroup[i]; + firstGroupData[i] = inIterators[indFirst].Get(); + } + + for (unsigned int i = 0; i < m_SecondGroupSize;++i) + { + unsigned int indSec = m_SecondGroup[i]; + secondGroupData[i] = inIterators[indSec].Get(); + } + + // Now that data is grouped, compute the distance matrix + for (unsigned int i = 0; i < m_FirstGroupSize;++i) + { + for (unsigned int l = 0;l < m_SecondGroupSize;++l) + { + double dist = 0; + for (unsigned int j = 0;j < vectorSize;++j) + dist += (firstGroupData[i][j] - secondGroupData[l][j]) * (firstGroupData[i][j] - secondGroupData[l][j]); + + cramerDistMatrix(i,m_FirstGroupSize + l) = sqrt(dist); + cramerDistMatrix(m_FirstGroupSize + l,i) = cramerDistMatrix(i,m_FirstGroupSize + l); + } + + for (unsigned int l = i+1;l < m_FirstGroupSize;++l) + { + double dist = 0; + for (unsigned int j = 0;j < vectorSize;++j) + dist += (firstGroupData[i][j] - firstGroupData[l][j]) * (firstGroupData[i][j] - firstGroupData[l][j]); + + cramerDistMatrix(i,l) = sqrt(dist); + cramerDistMatrix(l,i) = cramerDistMatrix(i,l); + } + } + + for (unsigned int i = 0; i < m_SecondGroupSize;++i) + { + for (unsigned int l = i+1;l < m_SecondGroupSize;++l) + { + double dist = 0; + for (unsigned int j = 0;j < vectorSize;++j) + dist += (secondGroupData[i][j] - secondGroupData[l][j]) * (secondGroupData[i][j] - secondGroupData[l][j]); + + cramerDistMatrix(m_FirstGroupSize + i,m_FirstGroupSize + l) = sqrt(dist); + cramerDistMatrix(m_FirstGroupSize + l,m_FirstGroupSize + i) = cramerDistMatrix(m_FirstGroupSize + i,m_FirstGroupSize + l); + } + } + + if (m_UseOutlierMasks) + { + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + inlierProbabilities[i] = (outlierMasksIterators[i].Get() == 0); + } + + outIterator.Set(this->BootStrap(cramerDistMatrix,inlierProbabilities)); + + ++outIterator; + ++maskIterator; + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + ++inIterators[i]; + + if (m_UseOutlierMasks) + { + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + ++outlierMasksIterators[i]; + } + + this->IncrementNumberOfProcessedPoints(); + } +} + +template +void +CramersTestImageFilter +::GenerateBootStrapSamples() +{ + m_SamplesFirstGroup.resize(m_NbSamples + 1); + m_SamplesSecondGroup.resize(m_NbSamples + 1); + + m_SamplesFirstGroup[0] = m_FirstGroup; + m_SamplesSecondGroup[0] = m_SecondGroup; + + unsigned int nbFirstGroup = m_FirstGroup.size(); + unsigned int nbSecondGroup = m_SecondGroup.size(); + unsigned int nbSubjects = nbFirstGroup + nbSecondGroup; + + unsigned int minNbGroup = std::min(nbFirstGroup,nbSecondGroup); + bool isFirstGroupMin = (nbFirstGroup <= nbSecondGroup); + + std::vector sampleGen, sampleGenOtherGroup; + std::mt19937 generator(time(0)); + + for (unsigned int i = 1;i <= m_NbSamples;++i) + { + sampleGen.clear(); + sampleGenOtherGroup.clear(); + + unsigned int j = 0; + while (j < minNbGroup) + { + int tmpVal = std::min((int)(nbSubjects - 1),(int)floor(anima::SampleFromUniformDistribution(0.0,1.0,generator) * nbSubjects)); + if (tmpVal < 0) + tmpVal = 0; + + bool isAlreadyIndexed = false; + for (unsigned int k = 0; k < j;++k) + { + if (tmpVal == sampleGen[k]) + { + isAlreadyIndexed = true; + break; + } + } + + if (!isAlreadyIndexed) + { + sampleGen.push_back(tmpVal); + j++; + } + } + + // New sample gen that is not already here... Add it to the result vectors + for (unsigned int j = 0;j < nbSubjects;++j) + { + bool isAlreadyIndexed = false; + for (unsigned int k = 0;k < minNbGroup;++k) + { + if (j == sampleGen[k]) + { + isAlreadyIndexed = true; + break; + } + } + + if (!isAlreadyIndexed) + sampleGenOtherGroup.push_back(j); + } + + if (isFirstGroupMin) + { + m_SamplesFirstGroup[i] = sampleGen; + m_SamplesSecondGroup[i] = sampleGenOtherGroup; + } + else + { + m_SamplesFirstGroup[i] = sampleGenOtherGroup; + m_SamplesSecondGroup[i] = sampleGen; + } + } +} + +template +double +CramersTestImageFilter +::BootStrap(vnl_matrix &groupDistMatrix, std::vector &inlierWeights) +{ + // First index is the true group separation (see GenerateBootStrap) + double dataVal = this->CramerStatistic(groupDistMatrix,inlierWeights,0); + + std::vector statsValues(m_NbSamples,0); + + for (unsigned long int i = 0;i < m_NbSamples;++i) + statsValues[i] = this->CramerStatistic(groupDistMatrix,inlierWeights,i+1); + + std::sort(statsValues.begin(),statsValues.end()); + + if (dataVal >= statsValues[statsValues.size() - 1]) + return 0; + + unsigned int position = 0; + + while(position < statsValues.size()) + { + if (dataVal <= statsValues[position]) + break; + + ++position; + } + + double resVal = 0; + + if (position > 0) + { + resVal = m_NbSamples - position + (statsValues[position - 1] - dataVal) / (statsValues[position] - statsValues[position - 1]); + resVal /= m_NbSamples; + } + else + { + // Here statsValues[position - 1] doesn't exist, replacing with 0 + resVal = m_NbSamples - position - dataVal / statsValues[position]; + resVal /= m_NbSamples; + } + + return resVal; +} + +template +double +CramersTestImageFilter +::CramerStatistic(vnl_matrix &grpDistMatrix, std::vector &inlierWeights, + unsigned int index) +{ + unsigned int nbFirstGroup = m_SamplesFirstGroup[index].size(); + unsigned int nbSecondGroup = m_SamplesSecondGroup[index].size(); + + double firstTerm = 0, secondTerm = 0, thirdTerm = 0; + double Z1 = 0, Z2 = 0, Z3 = 0; + + for (unsigned int i = 0; i < nbFirstGroup;++i) + { + unsigned int indFirst = m_SamplesFirstGroup[index][i]; + for (unsigned int j = 0;j < nbSecondGroup;++j) + { + double alpha = inlierWeights[indFirst]*inlierWeights[m_SamplesSecondGroup[index][j]]; + firstTerm += alpha*grpDistMatrix(indFirst,m_SamplesSecondGroup[index][j]); + Z1 += alpha; + } + + for (unsigned int j = i+1;j < nbFirstGroup;++j) + { + double alpha = 0.5*inlierWeights[indFirst]*inlierWeights[m_SamplesFirstGroup[index][j]]; + secondTerm += 2*alpha*grpDistMatrix(indFirst,m_SamplesFirstGroup[index][j]); + Z2 += 2*alpha; + } + + // Don't forget to add the diagonal term for Z2 + Z2 += inlierWeights[indFirst]*inlierWeights[m_SamplesFirstGroup[index][i]]; + } + + firstTerm /= Z1; + secondTerm /= (2*Z2); + + for (unsigned int i = 0; i < nbSecondGroup;++i) + { + int indSec = m_SamplesSecondGroup[index][i]; + for (unsigned int j = i+1;j < nbSecondGroup;++j) + { + double alpha = inlierWeights[indSec]*inlierWeights[m_SamplesSecondGroup[index][j]]; + thirdTerm += 2*alpha*grpDistMatrix(indSec,m_SamplesSecondGroup[index][j]); + Z3 += 2*alpha; + } + + // Don't forget to add the diagonal term for Z3 + Z3 += inlierWeights[indSec]*inlierWeights[m_SamplesSecondGroup[index][i]]; + } + + thirdTerm /= (2*Z3); + + return firstTerm - secondTerm - thirdTerm; +} + +} // end namespace anima diff --git a/Anima/math-tools/statistical_tests/cramers_test/CMakeLists.txt b/Anima/math-tools/statistical_tests/cramers_test/CMakeLists.txt new file mode 100644 index 000000000..dcd1b02a2 --- /dev/null +++ b/Anima/math-tools/statistical_tests/cramers_test/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaCramersTest) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistical_tests/cramers_test/animaCramersTest.cxx b/Anima/math-tools/statistical_tests/cramers_test/animaCramersTest.cxx new file mode 100644 index 000000000..f0820145c --- /dev/null +++ b/Anima/math-tools/statistical_tests/cramers_test/animaCramersTest.cxx @@ -0,0 +1,179 @@ +#include +#include + +#include +#include +#include +#include +#include + +//Update progression of the process +void eventCallback (itk::Object* caller, const itk::EventObject& event, void* clientData) +{ + itk::ProcessObject * processObject = (itk::ProcessObject*) caller; + std::cout<<"\033[K\rProgression: "<<(int)(processObject->GetProgress() * 100)<<"%"< logsArg("l","logfilelist","File containing the list of log-tensors",true,"","log-tensors list",cmd); + TCLAP::ValueArg maskArg("m","maskname","Computation mask",true,"","computation mask",cmd); + TCLAP::ValueArg outlierMasksArg("M","outliermasks","Outlier masks list",false,"","outlier masks list",cmd); + TCLAP::ValueArg resArg("o","outputname","Result image",true,"","result image",cmd); + + TCLAP::ValueArg fGrpArg("f","firstgroupfile","Text file containing the indexes of images from the first group",true,"","first group indexes list",cmd); + TCLAP::ValueArg sGrpArg("s","secondgroupfile","Text file containing the indexes of images from the second group",true,"","second group indexes list",cmd); + + TCLAP::ValueArg nbSamplesArg("n","nbbootstrapsamples","Number of permutation samples (default: 5000)",false,5000,"# permutation samples",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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + std::string logsName, maskName, resName; + logsName = logsArg.getValue(); + maskName = maskArg.getValue(); + resName = resArg.getValue(); + + unsigned int nbSamples = nbSamplesArg.getValue(); + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + typedef itk::ImageFileWriter < itk::Image > itkOutputWriter; + typedef itk::ImageFileReader < itk::Image > itkMaskReader; + itkMaskReader::Pointer maskRead = itkMaskReader::New(); + maskRead->SetFileName(maskName.c_str()); + maskRead->Update(); + + std::vector firstGroupIndexes, secGroupIndexes; + std::string fGrpName, sGrpName; + fGrpName = fGrpArg.getValue(); + sGrpName = sGrpArg.getValue(); + + if (strcmp(fGrpName.c_str(),"") != 0) + { + std::ifstream fGrpfile(fGrpName.c_str()); + while (!fGrpfile.eof()) + { + unsigned int tmpVal; + char tmpStr[2048]; + fGrpfile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") != 0) + { + sscanf(tmpStr,"%d",&tmpVal); + firstGroupIndexes.push_back(tmpVal); + } + } + + fGrpfile.close(); + } + + if (strcmp(sGrpName.c_str(),"") != 0) + { + std::ifstream sGrpfile(sGrpName.c_str()); + while (!sGrpfile.eof()) + { + unsigned int tmpVal; + char tmpStr[2048]; + sGrpfile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") != 0) + { + sscanf(tmpStr,"%d",&tmpVal); + secGroupIndexes.push_back(tmpVal); + } + } + + sGrpfile.close(); + } + + typedef itk::VectorImage LogTensorImageType; + typedef itk::ImageFileReader itkInputReader; + typedef anima::CramersTestImageFilter CramersFilterType; + + CramersFilterType::Pointer cramersFilter = CramersFilterType::New(); + cramersFilter->SetComputationMask(maskRead->GetOutput()); + cramersFilter->SetNbSamples(nbSamples); + + std::ifstream fileIn(logsName.c_str()); + + int nbPats = 0; + itkInputReader::Pointer imageReader; + + while (!fileIn.eof()) + { + char tmpStr[2048]; + fileIn.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading image " << nbPats << " " << tmpStr << "..." << std::endl; + imageReader = itkInputReader::New(); + imageReader->SetFileName(tmpStr); + imageReader->Update(); + + cramersFilter->SetInput(nbPats,imageReader->GetOutput()); + nbPats++; + } + fileIn.close(); + + if (outlierMasksArg.getValue() != "") + { + std::ifstream masksFile(outlierMasksArg.getValue().c_str()); + itkMaskReader::Pointer mReader; + + while (!masksFile.eof()) + { + char tmpStr[2048]; + masksFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + mReader = itkMaskReader::New(); + mReader->SetFileName(tmpStr); + mReader->Update(); + + cramersFilter->AddOutlierMask(mReader->GetOutput()); + } + + masksFile.close(); + } + + cramersFilter->SetFirstGroupIndexes(firstGroupIndexes); + cramersFilter->SetSecondGroupIndexes(secGroupIndexes); + + unsigned int nbProcs = nbpArg.getValue(); + cramersFilter->SetNumberOfWorkUnits(nbProcs); + + itk::CStyleCommand::Pointer callback = itk::CStyleCommand::New(); + callback->SetCallback(eventCallback); + cramersFilter->AddObserver(itk::ProgressEvent(), callback ); + + cramersFilter->Update(); + + itkOutputWriter::Pointer resultWriter = itkOutputWriter::New(); + resultWriter->SetFileName(resName.c_str()); + resultWriter->SetUseCompression(true); + resultWriter->SetInput(cramersFilter->GetOutput()); + + resultWriter->Update(); + + tmpTime.Stop(); + + std::cout << "Total computation time: " << tmpTime.GetTotal() << std::endl; + + return 0; +} diff --git a/Anima/math-tools/statistics/CMakeLists.txt b/Anima/math-tools/statistics/CMakeLists.txt index 3a496c394..1e8c08d91 100644 --- a/Anima/math-tools/statistics/CMakeLists.txt +++ b/Anima/math-tools/statistics/CMakeLists.txt @@ -9,6 +9,7 @@ if(BUILD_TESTING) add_subdirectory(gamma_estimation_test) endif() +add_subdirectory(boot_strap_4d_volume) add_subdirectory(local_patch_covariance_distance) add_subdirectory(local_patch_mean_distance) add_subdirectory(low_memory_tools) diff --git a/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.h b/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.h new file mode 100644 index 000000000..7df686678 --- /dev/null +++ b/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +namespace anima +{ +template +class Bootstrap4DVolumeImageFilter : + public itk::ImageToImageFilter< itk::Image , itk::Image > +{ +public: + /** Standard class typedefs. */ + typedef Bootstrap4DVolumeImageFilter Self; + typedef itk::Image TInputImage; + typedef itk::Image 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(Bootstrap4DVolumeImageFilter, ImageToImageFilter) + + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TInputImage::PixelType InputImagePixel; + typedef typename TOutputImage::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + void SetNthInformationFile(unsigned int i, std::string fileName); + + std::vector &GetOutputInformation() {return m_OutputInformation;} + +protected: + Bootstrap4DVolumeImageFilter() + { + m_ImageInformations.clear(); + m_OutputInformation.clear(); + } + + virtual ~Bootstrap4DVolumeImageFilter() {} + + void GenerateData() ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(Bootstrap4DVolumeImageFilter); + + std::vector < std::vector > m_ImageInformations; + std::vector m_OutputInformation; +}; + +} // end of namespace anima + +#include "animaBootstrap4DVolumeImageFilter.hxx" diff --git a/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.hxx b/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.hxx new file mode 100644 index 000000000..25f5ac0c4 --- /dev/null +++ b/Anima/math-tools/statistics/animaBootstrap4DVolumeImageFilter.hxx @@ -0,0 +1,94 @@ +#pragma once + +#include "animaBootstrap4DVolumeImageFilter.h" + +#include +#include +#include + +#include + +namespace anima +{ +template +void +Bootstrap4DVolumeImageFilter +::SetNthInformationFile(unsigned int i, std::string fileName) +{ + std::ifstream infoFile(fileName.c_str()); + if (!infoFile.is_open()) + { + throw itk::ExceptionObject(__FILE__, __LINE__,"Please provide valid filename with information...",ITK_LOCATION); + } + + std::vector infoData; + while (!infoFile.eof()) + { + char tmpStr[2048]; + infoFile.getline(tmpStr,2048); + + infoData.push_back(tmpStr); + } + + infoFile.close(); + + if (i == m_ImageInformations.size()) + m_ImageInformations.push_back(infoData); + else if (i > m_ImageInformations.size()) + { + throw itk::ExceptionObject(__FILE__, __LINE__,"Trying to add a direction not contiguous... Add directions contiguously (0,1,2,3,...)",ITK_LOCATION); + } + else + m_ImageInformations[i] = infoData; + +} + +template +void +Bootstrap4DVolumeImageFilter +::GenerateData() +{ + this->AllocateOutputs(); + + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + // We suppose the 4th dimension of the volume is the temporal dimension + unsigned int numVolumes = this->GetInput(0)->GetLargestPossibleRegion().GetSize()[3]; + + if (m_ImageInformations.size() != 0) + m_OutputInformation.resize(numVolumes); + + srand(time(0)); + + typedef typename TInputImage::RegionType RegionType; + + RegionType region = this->GetInput(0)->GetLargestPossibleRegion(); + region.SetSize(3,1); + + typedef typename itk::ImageRegionConstIterator InputImageIteratorType; + typedef typename itk::ImageRegionIterator OutputImageIteratorType; + + for (unsigned int i = 0;i < numVolumes;++i) + { + unsigned int randSample = (unsigned int) floor(rand() * (double)numInputs / (double)(RAND_MAX)); + + if (m_OutputInformation.size() != 0) + m_OutputInformation[i] = m_ImageInformations[randSample][i]; + + region.SetIndex(3,i); + + InputImageIteratorType inItr(this->GetInput(randSample), region); + OutputImageIteratorType outItr(this->GetOutput(), region); + + while (!inItr.IsAtEnd()) + { + outItr.Set(inItr.Get()); + + ++inItr; + ++outItr; + } + } +} + +} // end namespace anima + + diff --git a/Anima/math-tools/statistics/boot_strap_4d_volume/CMakeLists.txt b/Anima/math-tools/statistics/boot_strap_4d_volume/CMakeLists.txt new file mode 100644 index 000000000..e32369dff --- /dev/null +++ b/Anima/math-tools/statistics/boot_strap_4d_volume/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaBootstrap4DVolume) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistics/boot_strap_4d_volume/animaBootstrap4DVolume.cxx b/Anima/math-tools/statistics/boot_strap_4d_volume/animaBootstrap4DVolume.cxx new file mode 100644 index 000000000..5ca40122c --- /dev/null +++ b/Anima/math-tools/statistics/boot_strap_4d_volume/animaBootstrap4DVolume.cxx @@ -0,0 +1,100 @@ +#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","inputlist","File containing a list of 4D images",true,"","input 4D images",cmd); + TCLAP::ValueArg infoArg("l","infolist","File containing a list of info on each 4D image (one info line per sub-3D volume)",false,"","info list on 4D images",cmd); + TCLAP::ValueArg resArg("o","outputfile","Result sampled 4D image",true,"","result 4D image",cmd); + TCLAP::ValueArg resInfoArg("O","outputinfo","Result information gathered from input information",false,"","result info text file",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef anima::Bootstrap4DVolumeImageFilter MainFilterType; + + typedef itk::ImageFileReader < MainFilterType::TInputImage > ImageReaderType; + typedef itk::ImageFileWriter < MainFilterType::TOutputImage > ImageWriterType; + + MainFilterType::Pointer mainFilter = MainFilterType::New(); + + std::ifstream inputFile(inArg.getValue().c_str()); + + if (!inputFile.is_open()) + { + std::cerr << "Please provide usable file with input files" << std::endl; + return -1; + } + + unsigned int numInput = 0; + while (!inputFile.eof()) + { + char tmpStr[2048]; + inputFile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + ImageReaderType::Pointer tmpReader = ImageReaderType::New(); + tmpReader->SetFileName(tmpStr); + tmpReader->Update(); + mainFilter->SetInput(numInput,tmpReader->GetOutput()); + numInput++; + } + + if (infoArg.getValue() != "") + { + std::ifstream infoList(infoArg.getValue().c_str()); + + unsigned int i = 0; + while (!infoList.eof()) + { + char tmpStr[2048]; + infoList.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + mainFilter->SetNthInformationFile(i,tmpStr); + ++i; + } + + infoList.close(); + } + + 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(); + + if ((resInfoArg.getValue() != "")&&(mainFilter->GetOutputInformation().size() != 0)) + { + std::ofstream outInfo(resInfoArg.getValue().c_str()); + for (unsigned int i = 0;i < mainFilter->GetOutputInformation().size();++i) + outInfo << mainFilter->GetOutputInformation()[i] << std::endl; + + outInfo.close(); + } + + return 0; +} From 76eb01be127af73fa605b743b28d9d369e9821b7 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 14:24:47 +0200 Subject: [PATCH 07/17] Forgotten math-tools elements --- .../low_memory_tools/CMakeLists.txt | 1 + .../cramers_test/CMakeLists.txt | 36 +++ .../cramers_test/animaLowMemCramersTest.cxx | 78 ++++++ .../animaLowMemCramersTestBridge.cxx | 261 ++++++++++++++++++ .../animaLowMemCramersTestBridge.h | 51 ++++ 5 files changed, 427 insertions(+) create mode 100644 Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/CMakeLists.txt create mode 100644 Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTest.cxx create mode 100644 Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.cxx create mode 100644 Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.h diff --git a/Anima/math-tools/statistical_tests/low_memory_tools/CMakeLists.txt b/Anima/math-tools/statistical_tests/low_memory_tools/CMakeLists.txt index aac6359eb..9058da1b2 100644 --- a/Anima/math-tools/statistical_tests/low_memory_tools/CMakeLists.txt +++ b/Anima/math-tools/statistical_tests/low_memory_tools/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(cramers_test) add_subdirectory(nlmeans_patient_to_group_comparison) add_subdirectory(patient_to_group_odf_comparison) add_subdirectory(patient_to_group_comparison) \ No newline at end of file diff --git a/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/CMakeLists.txt b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/CMakeLists.txt new file mode 100644 index 000000000..48b1a1b5e --- /dev/null +++ b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaLowMemCramersTest) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTest.cxx b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTest.cxx new file mode 100644 index 000000000..d88e833b4 --- /dev/null +++ b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTest.cxx @@ -0,0 +1,78 @@ +#include +#include + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg logsArg("l","logfilelist","File containing the list of log-tensors",true,"","log-tensors list",cmd); + TCLAP::ValueArg maskArg("m","maskname","Computation mask",true,"","computation mask",cmd); + TCLAP::ValueArg outlierMasksArg("M","outliermasks","Outlier masks list",false,"","outlier masks list",cmd); + TCLAP::ValueArg resArg("o","outputprefix","Result image prefix",true,"","result image prefix",cmd); + + TCLAP::ValueArg fGrpArg("","fg","Text file containing the indexes of images from the first group",true,"","first group indexes list",cmd); + TCLAP::ValueArg sGrpArg("","sg","Text file containing the indexes of images from the second group",true,"","second group indexes list",cmd); + + TCLAP::ValueArg nbSamplesArg("n","nbbootstrapsamples","Number of permutation samples (default: 5000)",false,5000,"# permutation samples",cmd); + TCLAP::ValueArg nbpArg("p","numberofthreads","Number of threads to run on (default : all cores)",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"number of threads",cmd); + + TCLAP::ValueArg splitsArg("s","split","Split image for low memory (default: 2)",false,2,"Number of splits",cmd); + TCLAP::ValueArg specSplitArg("S","splittoprocess","Specific split to process (use to run on cluster (default: -1 = all)",false,-1,"Split to process",cmd); + TCLAP::SwitchArg genOutputDescroArg("G","generateouputdescription","Generate ouptut description data",cmd,false);; + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef anima::LowMemoryCramersTestBridge CramersBridgeType; + + std::string logsName, maskName, resName; + logsName = logsArg.getValue(); + maskName = maskArg.getValue(); + resName = resArg.getValue(); + + unsigned int nbSamples = nbSamplesArg.getValue(); + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + std::string fGrpName, sGrpName; + fGrpName = fGrpArg.getValue(); + sGrpName = sGrpArg.getValue(); + + CramersBridgeType *cramersFilter = new CramersBridgeType; + cramersFilter->SetComputationMask(maskName); + cramersFilter->SetNbSamples(nbSamples); + + cramersFilter->SetInputFileNames(logsName); + if (outlierMasksArg.getValue() != "") + cramersFilter->SetOutlierMaskFileNames(outlierMasksArg.getValue()); + + cramersFilter->SetNbSplits(splitsArg.getValue()); + cramersFilter->SetOutputPrefix(resName); + cramersFilter->SetIndexesFromFiles(fGrpName,sGrpName); + + unsigned int nbProcs = nbpArg.getValue(); + cramersFilter->SetNumberOfThreads(nbProcs); + + cramersFilter->Update(specSplitArg.getValue(),genOutputDescroArg.getValue()); + + tmpTime.Stop(); + + std::cout << "Total computation time: " << tmpTime.GetTotal() << std::endl; + + delete cramersFilter; + + return 0; +} diff --git a/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.cxx b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.cxx new file mode 100644 index 000000000..2370e1a80 --- /dev/null +++ b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.cxx @@ -0,0 +1,261 @@ +#include "animaLowMemCramersTestBridge.h" +#include +#include + +namespace anima +{ + +LowMemoryCramersTestBridge::LowMemoryCramersTestBridge() +{ + m_NbSplits = 2; + m_NumThreads = itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(); + + m_InputImages = new ImageSplitterType; + m_OutlierMaskImages = ITK_NULLPTR; + m_ComputationMask = ITK_NULLPTR; + + m_OutputPrefix = ""; + m_FirstGroupIndexes.clear(); + m_SecondGroupIndexes.clear(); + m_NbSamples = 5000; +} + +LowMemoryCramersTestBridge::~LowMemoryCramersTestBridge() +{ + if (m_InputImages) + delete m_InputImages; + + if (m_OutlierMaskImages) + delete m_OutlierMaskImages; +} + +void LowMemoryCramersTestBridge::SetInputFileNames(std::string &fileList) +{ + m_InputImages->SetFileNames(fileList); +} + +void LowMemoryCramersTestBridge::SetOutlierMaskFileNames(std::string &fileList) +{ + if (!m_OutlierMaskImages) + m_OutlierMaskImages = new MaskImageSplitterType; + + m_OutlierMaskImages->SetFileNames(fileList); +} + +void LowMemoryCramersTestBridge::SetComputationMask(std::string &cMask) +{ + itk::ImageFileReader ::Pointer tmpRead = itk::ImageFileReader ::New(); + tmpRead->SetFileName(cMask); + tmpRead->Update(); + + m_ComputationMask = tmpRead->GetOutput(); +} + +void LowMemoryCramersTestBridge::SetIndexesFromFiles(std::string firstFile, std::string secondFile) +{ + m_FirstGroupIndexes.clear(); + m_SecondGroupIndexes.clear(); + + std::ifstream fGrpfile(firstFile.c_str()); + + if (fGrpfile.is_open()) + { + while (!fGrpfile.eof()) + { + unsigned int tmpVal; + char tmpStr[2048]; + fGrpfile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") != 0) + { + sscanf(tmpStr,"%d",&tmpVal); + m_FirstGroupIndexes.push_back(tmpVal); + } + } + + fGrpfile.close(); + } + + std::ifstream sGrpfile(secondFile.c_str()); + if (sGrpfile.is_open()) + { + while (!sGrpfile.eof()) + { + unsigned int tmpVal; + char tmpStr[2048]; + sGrpfile.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") != 0) + { + sscanf(tmpStr,"%d",&tmpVal); + m_SecondGroupIndexes.push_back(tmpVal); + } + } + + sGrpfile.close(); + } +} + +void LowMemoryCramersTestBridge::Update(int specificSplitToDo, bool genOutputDescriptionData) +{ + if (!m_ComputationMask) + itkExceptionMacro("No computation mask..."); + + m_InputImages->SetComputationMask(m_ComputationMask); + + if (m_OutlierMaskImages) + m_OutlierMaskImages->SetComputationMask(m_ComputationMask); + + ImageSplitterType::TInputIndexType tmpInd; + for (unsigned int i = 0;i < MaskImageType::GetImageDimension();++i) + tmpInd[i] = m_NbSplits; + + m_InputImages->SetNumberOfBlocks(tmpInd); + + if (m_OutlierMaskImages) + m_OutlierMaskImages->SetNumberOfBlocks(tmpInd); + + std::vector < ImageSplitterType::TInputIndexType > splitIndexesToProcess; + + if (specificSplitToDo != -1) + { + tmpInd[0] = (unsigned int)floor((double)(specificSplitToDo/(m_NbSplits*m_NbSplits))); + unsigned int tmpVal = specificSplitToDo - tmpInd[0]*m_NbSplits*m_NbSplits; + tmpInd[1] = (unsigned int)floor((double)(tmpVal/m_NbSplits)); + tmpInd[2] = tmpVal - tmpInd[1]*m_NbSplits; + + if (!m_InputImages->EmptyMask(tmpInd)) + splitIndexesToProcess.push_back(tmpInd); + } + else + { + for (unsigned int i = 0;i < m_NbSplits;++i) + { + tmpInd[0] = i; + for (unsigned int j = 0;j < m_NbSplits;++j) + { + tmpInd[1] = j; + for (unsigned int k = 0;k < m_NbSplits;++k) + { + tmpInd[2] = k; + + if (!m_InputImages->EmptyMask(tmpInd)) + splitIndexesToProcess.push_back(tmpInd); + } + } + } + } + + for (unsigned int i = 0;i < splitIndexesToProcess.size();++i) + { + std::cout << "Processing block : " << splitIndexesToProcess[i][0] << " " + << splitIndexesToProcess[i][1] << " " << splitIndexesToProcess[i][2] << std::endl; + + m_InputImages->SetBlockIndex(splitIndexesToProcess[i]); + m_InputImages->Update(); + + if (m_OutlierMaskImages) + { + m_OutlierMaskImages->SetBlockIndex(splitIndexesToProcess[i]); + m_OutlierMaskImages->Update(); + } + + MainFilterType::Pointer cramersFilter = MainFilterType::New(); + + for (unsigned int j = 0;j < m_InputImages->GetNbImages();++j) + { + cramersFilter->SetInput(j,m_InputImages->GetOutput(j)); + + if (m_OutlierMaskImages) + cramersFilter->AddOutlierMask(m_OutlierMaskImages->GetOutput(j)); + } + + cramersFilter->SetComputationMask(m_InputImages->GetSmallMaskWithMargin()); + cramersFilter->SetNbSamples(m_NbSamples); + cramersFilter->SetNumberOfWorkUnits(m_NumThreads); + cramersFilter->SetFirstGroupIndexes(m_FirstGroupIndexes); + cramersFilter->SetSecondGroupIndexes(m_SecondGroupIndexes); + + cramersFilter->Update(); + + std::cout << "Results computed... Writing reference standard, bias and covariance images..." << std::endl; + + char numSplit[2048]; + sprintf(numSplit,"_%ld_%ld_%ld.nrrd",splitIndexesToProcess[i][0],splitIndexesToProcess[i][1],splitIndexesToProcess[i][2]); + + //Write main output + std::string outputName = m_OutputPrefix + numSplit; + this->BuildAndWrite(cramersFilter->GetOutput(),outputName,m_InputImages->GetBlockRegionInsideMargin()); + } + + if (genOutputDescriptionData) + { + std::string mainOutDescroName = m_OutputPrefix + ".txt"; + std::ofstream mainOutFile(mainOutDescroName.c_str()); + + for (unsigned int i = 0;i < m_NbSplits;++i) + { + tmpInd[0] = i; + for (unsigned int j = 0;j < m_NbSplits;++j) + { + tmpInd[1] = j; + for (unsigned int k = 0;k < m_NbSplits;++k) + { + tmpInd[2] = k; + + if (!m_InputImages->EmptyMask(tmpInd)) + { + OutputImageType::RegionType tmpBlRegion = m_InputImages->GetSpecificBlockRegion(tmpInd); + char numSplit[2048]; + sprintf(numSplit,"_%ld_%ld_%ld.nrrd",tmpInd[0],tmpInd[1],tmpInd[2]); + + mainOutFile << "" << std::endl; + mainOutFile << "BLOCK_FILE=" << m_OutputPrefix + numSplit << std::endl; + mainOutFile << "STARTING_INDEX=" << tmpBlRegion.GetIndex()[0] << " " + << tmpBlRegion.GetIndex()[1] << " " << tmpBlRegion.GetIndex()[2] << std::endl; + mainOutFile << "" << std::endl; + } + } + } + } + mainOutFile.close(); + } +} + +void LowMemoryCramersTestBridge::BuildAndWrite(OutputImageType *tmpIm, std::string resName, + OutputImageType::RegionType finalROI) +{ + OutputImageType::RegionType tmpRegion = finalROI; + for (unsigned int i = 0;i < OutputImageType::GetImageDimension();++i) + tmpRegion.SetIndex(i,0); + + OutputImageType::Pointer tmpRes = OutputImageType::New(); + tmpRes->Initialize(); + + tmpRes->SetOrigin(m_ComputationMask->GetOrigin()); + tmpRes->SetRegions(tmpRegion); + tmpRes->SetDirection(m_ComputationMask->GetDirection()); + tmpRes->SetSpacing(m_ComputationMask->GetSpacing()); + + tmpRes->Allocate(); + + itk::ImageRegionIterator tmpImIt (tmpIm,finalROI); + itk::ImageRegionIterator tmpResIt (tmpRes,tmpRegion); + + while (!tmpImIt.IsAtEnd()) + { + tmpResIt.Set(tmpImIt.Get()); + + ++tmpImIt; + ++tmpResIt; + } + + itk::ImageFileWriter ::Pointer outWriter = itk::ImageFileWriter ::New(); + outWriter->SetInput(tmpRes); + outWriter->SetFileName(resName); + outWriter->SetUseCompression(true); + + outWriter->Update(); +} + +} // end namesapce anima diff --git a/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.h b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.h new file mode 100644 index 000000000..e08d4b54f --- /dev/null +++ b/Anima/math-tools/statistical_tests/low_memory_tools/cramers_test/animaLowMemCramersTestBridge.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +namespace anima +{ + +class LowMemoryCramersTestBridge +{ +public: + typedef anima::CramersTestImageFilter::TInputImage InputImageType; + typedef anima::CramersTestImageFilter::TOutputImage OutputImageType; + typedef anima::ImageDataSplitter < InputImageType > ImageSplitterType; + typedef anima::CramersTestImageFilter MainFilterType; + typedef MainFilterType::MaskImageType MaskImageType; + typedef anima::ImageDataSplitter < MaskImageType > MaskImageSplitterType; + + LowMemoryCramersTestBridge(); + ~LowMemoryCramersTestBridge(); + + std::string GetNameOfClass() {return "LowMemoryCramersTestBridge";} + + void SetInputFileNames(std::string &fileList); + void SetOutlierMaskFileNames(std::string &fileList); + void SetComputationMask(std::string &cMask); + + void SetOutputPrefix(std::string &pref) {m_OutputPrefix = pref;} + + void SetNbSplits(unsigned int nbSplits) {m_NbSplits = nbSplits;} + void SetNumberOfThreads(unsigned int &nbT) {m_NumThreads = nbT;} + void SetNbSamples(unsigned int nbS) {m_NbSamples = nbS;} + + void SetIndexesFromFiles(std::string firstFile, std::string secondFile); + + void Update(int specificSplitToDo = -1, bool genOutputDescriptionData = false); + void BuildAndWrite(OutputImageType *tmpIm, std::string resName, OutputImageType::RegionType finalROI); + +private: + std::string m_OutputPrefix; + std::vector m_FirstGroupIndexes, m_SecondGroupIndexes; + unsigned int m_NbSplits; + unsigned int m_NumThreads; + unsigned int m_NbSamples; + + ImageSplitterType *m_InputImages; + MaskImageSplitterType *m_OutlierMaskImages; + MaskImageType::Pointer m_ComputationMask; +}; + +} // end namespace anima From 5df905bfecf8939a38f5d16cd33871450a5b6868 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 14:33:59 +0200 Subject: [PATCH 08/17] finish fusion filtering --- Anima/filtering/denoising/CMakeLists.txt | 3 +- .../CMakeLists.txt | 36 +++ .../animaPseudoResidualsNoiseEstimation.cxx | 165 +++++++++++ .../animaPseudoResidualsNoiseImageFilter.h | 58 ++++ .../animaPseudoResidualsNoiseImageFilter.hxx | 86 ++++++ ...InhomogeneousAOSLineDiffusionImageFilter.h | 266 ++++++++++++++++++ ...homogeneousAOSLineDiffusionImageFilter.hxx | 233 +++++++++++++++ .../animaInhomogeneousDiffusionImageFilter.h | 101 +++++++ ...animaInhomogeneousDiffusionImageFilter.hxx | 174 ++++++++++++ 9 files changed, 1121 insertions(+), 1 deletion(-) create mode 100644 Anima/filtering/denoising/pseudo_residuals_noise_estimation/CMakeLists.txt create mode 100644 Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseEstimation.cxx create mode 100644 Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.h create mode 100644 Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.hxx create mode 100644 Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.h create mode 100644 Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.hxx create mode 100644 Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.h create mode 100644 Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.hxx diff --git a/Anima/filtering/denoising/CMakeLists.txt b/Anima/filtering/denoising/CMakeLists.txt index e80289886..53175b8cc 100644 --- a/Anima/filtering/denoising/CMakeLists.txt +++ b/Anima/filtering/denoising/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(non_local_means_image_filter) -add_subdirectory(non_local_means_temporal_image_filter) \ No newline at end of file +add_subdirectory(non_local_means_temporal_image_filter) +add_subdirectory(pseudo_residuals_noise_estimation) diff --git a/Anima/filtering/denoising/pseudo_residuals_noise_estimation/CMakeLists.txt b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/CMakeLists.txt new file mode 100644 index 000000000..daf0e0a27 --- /dev/null +++ b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaPseudoResidualsNoiseEstimation) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseEstimation.cxx b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseEstimation.cxx new file mode 100644 index 000000000..56e48f56e --- /dev/null +++ b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseEstimation.cxx @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include + +#include + +int main(int argc, const char** argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg inArg("i","input","input image",true,"","input image",cmd); + TCLAP::ValueArg resArg("o","output","Result noise image",true,"","result noise image",cmd); + + TCLAP::ValueArg nbpArg("T","nb-threads","Number of threads (default: all cores)",false,itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads(),"Number of threads",cmd); + TCLAP::ValueArg radiusArg("r","radius","Radius for pseudo-residuals (default: 1)",false,1,"neighborhood radius",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef double PixelType; + typedef itk::Image < PixelType, 4 > Image4DType; + typedef itk::Image < PixelType, 3 > ImageType; + typedef itk::ImageFileReader < Image4DType > Reader4DType; + typedef itk::ImageFileReader < ImageType > ReaderType; + typedef itk::ImageFileWriter < Image4DType > Writer4DType; + typedef itk::ImageFileWriter < ImageType > WriterType; + typedef anima::PseudoResidualsNoiseImageFilter FilterType; + + itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(inArg.getValue().c_str(), + itk::IOFileModeEnum::ReadMode); + + if (!imageIO) + { + std::cerr << "Itk could not find suitable IO factory for the input" << std::endl; + return EXIT_FAILURE; + } + + // 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(); + + unsigned int ndim = imageIO->GetNumberOfDimensions(); + + if (ndim < 4) //Image is 3D + { + ReaderType::Pointer reader3D = ReaderType::New(); + reader3D->SetImageIO(imageIO); + reader3D->SetFileName(inArg.getValue()); + reader3D->Update(); + + FilterType::Pointer tmpFilter = FilterType::New(); + tmpFilter->SetInput(reader3D->GetOutput()); + tmpFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + tmpFilter->SetPatchHalfSize(radiusArg.getValue()); + + tmpFilter->Update(); + + // Now write output 4D image + WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(resArg.getValue()); + writer->SetInput(tmpFilter->GetOutput()); + writer->SetUseCompression(true); + writer->Update(); + } + else // 4D case + { + Reader4DType::Pointer reader = Reader4DType::New(); + reader->SetImageIO(imageIO); + reader->SetFileName(inArg.getValue()); + reader->Update(); + + Image4DType::Pointer in4DImage = reader->GetOutput(); + + ndim = in4DImage->GetLargestPossibleRegion().GetSize()[3]; + + Image4DType::RegionType region4d = in4DImage->GetLargestPossibleRegion(); + ImageType::RegionType region3d; + ImageType::SizeType size3d; + ImageType::SpacingType spacing3d; + ImageType::PointType origin3d; + ImageType::DirectionType direction3d; + + for (unsigned int i = 0;i < 3;++i) + { + region3d.SetIndex(i,region4d.GetIndex()[i]); + region3d.SetSize(i,region4d.GetSize()[i]); + + size3d[i] = region4d.GetSize()[i]; + spacing3d[i] = in4DImage->GetSpacing()[i]; + origin3d[i] = in4DImage->GetOrigin()[i]; + for (unsigned int j = 0;j < 3;++j) + direction3d(i,j) = (in4DImage->GetDirection())(i,j); + } + + typedef itk::ImageRegionIterator ImageIteratorType; + typedef itk::ImageRegionConstIterator ImageConstIteratorType; + typedef itk::ImageRegionConstIterator Image4DConstIteratorType; + typedef itk::ImageRegionIterator Image4DIteratorType; + + for (unsigned int i = 0;i < ndim;++i) + { + region4d.SetIndex(3,i); + region4d.SetSize(3,1); + + ImageType::Pointer tmpImage = ImageType::New(); + tmpImage->Initialize(); + tmpImage->SetRegions(region3d); + tmpImage->SetSpacing (spacing3d); + tmpImage->SetOrigin (origin3d); + tmpImage->SetDirection (direction3d); + tmpImage->Allocate(); + + ImageIteratorType fillItr(tmpImage,region3d); + Image4DConstIteratorType inItr(in4DImage,region4d); + + // Extract n-th 3D volume + while (!fillItr.IsAtEnd()) + { + fillItr.Set(inItr.Get()); + + ++fillItr; + ++inItr; + } + + // Run filter + FilterType::Pointer tmpFilter = FilterType::New(); + tmpFilter->SetInput(tmpImage); + tmpFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + tmpFilter->SetPatchHalfSize(radiusArg.getValue()); + + tmpFilter->Update(); + + // Get the output and copy it back to input image + Image4DIteratorType outItr(in4DImage,region4d); + ImageConstIteratorType filteredItr(tmpFilter->GetOutput(),region3d); + + while (!outItr.IsAtEnd()) + { + outItr.Set(filteredItr.Get()); + + ++outItr; + ++filteredItr; + } + } + + // Now write output 4D image + Writer4DType::Pointer writer = Writer4DType::New(); + writer->SetFileName(resArg.getValue()); + writer->SetInput(in4DImage); + writer->SetUseCompression(true); + writer->Update(); + } + + return EXIT_SUCCESS; +} diff --git a/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.h b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.h new file mode 100644 index 000000000..8983ab7c9 --- /dev/null +++ b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +namespace anima +{ + +template +class PseudoResidualsNoiseImageFilter : + public itk::ImageToImageFilter< TInputImage, TOutputImage > +{ +public: + /** Standard class typedefs. */ + typedef PseudoResidualsNoiseImageFilter Self; + 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(PseudoResidualsNoiseImageFilter, ImageToImageFilter) + + /** Image typedef support */ + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename InputImageType::IndexType InputImageIndexType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + itkSetMacro(PatchHalfSize, unsigned int) + +protected: + PseudoResidualsNoiseImageFilter() + : Superclass() + { + m_PatchHalfSize = 1; + } + + virtual ~PseudoResidualsNoiseImageFilter() {} + + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(PseudoResidualsNoiseImageFilter); + + unsigned int m_PatchHalfSize; +}; + +} // end namespace anima + +#include "animaPseudoResidualsNoiseImageFilter.hxx" diff --git a/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.hxx b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.hxx new file mode 100644 index 000000000..546dcc308 --- /dev/null +++ b/Anima/filtering/denoising/pseudo_residuals_noise_estimation/animaPseudoResidualsNoiseImageFilter.hxx @@ -0,0 +1,86 @@ +#pragma once + +#include "animaPseudoResidualsNoiseImageFilter.h" + +#include +#include + +namespace anima +{ + +template +void +PseudoResidualsNoiseImageFilter:: +DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + typedef itk::ImageRegionConstIteratorWithIndex ImageIteratorType; + + typedef itk::ImageRegionIteratorWithIndex OutImageIteratorType; + OutImageIteratorType outIterator(this->GetOutput(),outputRegionForThread); + + InputImageRegionType localRegion; + InputImageRegionType localRegionInside; + InputImageIndexType baseIndex; + InputImageRegionType largestRegion = this->GetInput()->GetLargestPossibleRegion(); + + while (!outIterator.IsAtEnd()) + { + baseIndex = outIterator.GetIndex(); + + for (unsigned int i = 0;i < InputImageType::ImageDimension;++i) + { + localRegion.SetIndex(i,std::max(0,(int)(baseIndex[i] - m_PatchHalfSize))); + localRegion.SetSize(i,std::min((unsigned int)(largestRegion.GetSize()[i] - 1),(unsigned int)(baseIndex[i] + m_PatchHalfSize)) - localRegion.GetIndex(i) + 1); + } + + unsigned int numLocalPixels = 1; + + for (unsigned int i = 0;i < InputImageType::ImageDimension;++i) + numLocalPixels *= localRegion.GetSize()[i]; + + ImageIteratorType localIterator(this->GetInput(),localRegion); + + double localVariance = 0; + + while (!localIterator.IsAtEnd()) + { + baseIndex = localIterator.GetIndex(); + double centerVal = localIterator.Get(); + + for (unsigned int i = 0;i < InputImageType::ImageDimension;++i) + { + localRegionInside.SetIndex(i,std::max(0,(int)(baseIndex[i] - 1))); + localRegionInside.SetSize(i,std::min((unsigned int)(largestRegion.GetSize()[i] - 1),(unsigned int)(baseIndex[i] + 1)) - localRegionInside.GetIndex(i) + 1); + } + + ImageIteratorType insideIterator(this->GetInput(), localRegionInside); + + unsigned int numInsidePixels = 1; + + for (unsigned int i = 0;i < InputImageType::ImageDimension;++i) + numInsidePixels *= localRegionInside.GetSize()[i]; + + double epsilon = centerVal; + + while (!insideIterator.IsAtEnd()) + { + epsilon -= insideIterator.Get() / (numInsidePixels - 1.0); + + ++insideIterator; + } + + epsilon *= sqrt((numInsidePixels - 1.0) / numInsidePixels); + + localVariance += epsilon * epsilon; + + ++localIterator; + } + + localVariance /= numLocalPixels; + outIterator.Set(localVariance); + + ++outIterator; + } +} + +} // end namespace anima diff --git a/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.h b/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.h new file mode 100644 index 000000000..00a2b7424 --- /dev/null +++ b/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.h @@ -0,0 +1,266 @@ +#pragma once + +#include +#include +#include +#include + +namespace anima +{ + +template +class InhomogeneousAOSLineDiffusionImageFilter : + public itk::ImageToImageFilter +{ +public: + /** Standard class typedefs. */ + typedef InhomogeneousAOSLineDiffusionImageFilter Self; + typedef itk::ImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** Type macro that defines a name for this class. */ + itkTypeMacro(InhomogeneousAOSLineDiffusionImageFilter, ImageToImageFilter) + + /** Smart pointer typedef support. */ + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TInputImage::ConstPointer InputImageConstPointer; + + /** Real type to be used in internal computations. RealType in general is + * templated over the pixel type. (For example for vector or tensor pixels, + * RealType is a vector or a tensor of doubles.) ScalarRealType is a type + * meant for scalars. + */ + typedef typename TInputImage::PixelType InputPixelType; + typedef typename itk::NumericTraits::RealType RealType; + typedef typename itk::NumericTraits::ScalarRealType ScalarRealType; + + typedef typename TOutputImage::RegionType OutputImageRegionType; + + /** Type of the input image */ + typedef TInputImage InputImageType; + + typedef TDiffusionScalarImage DiffusionScalarsImageType; + typedef typename DiffusionScalarsImageType::Pointer DiffusionScalarsImagePointer; + typedef TOutputImage OutputImageType; + + /** Get the direction in which the filter is to be applied. */ + itkGetConstMacro(Direction, unsigned int) + + /** Set the direction in which the filter is to be applied. */ + itkSetMacro(Direction, unsigned int) + + /** Set Input Image. */ + void SetInputImage( const TInputImage * ); + + /** Get Input Image. */ + const TInputImage * GetInputImage(); + + itkSetMacro(TimeStep, double) + void SetDiffusionScalarsImage (DiffusionScalarsImageType *data) {m_DiffusionScalarsImage = data;} + +protected: + InhomogeneousAOSLineDiffusionImageFilter(); + virtual ~InhomogeneousAOSLineDiffusionImageFilter() {} + void PrintSelf(std::ostream& os, itk::Indent indent) const ITK_OVERRIDE; + + /** GenerateData (apply) the filter. */ + void GenerateData() ITK_OVERRIDE; + void BeforeThreadedGenerateData() ITK_OVERRIDE; + void DynamicThreadedGenerateData(const OutputImageRegionType& outputRegionForThread) ITK_OVERRIDE; + + /** InhomogeneousAOSLineDiffusionImageFilter needs all of the input only in the + * "Direction" dimension. Therefore we enlarge the output's + * RequestedRegion to this. Then the superclass's + * GenerateInputRequestedRegion method will copy the output region + * to the input. + * + * \sa ImageToImageFilter::GenerateInputRequestedRegion() + */ + void EnlargeOutputRequestedRegion(itk::DataObject *output) ITK_OVERRIDE; + + /** Apply the filter to an array of data. This method is called + * for each line of the volume. Parameter "scratch" is a scratch + * area used for internal computations that is the same size as the + * parameters "outs" and "data". l_coefs, m_coefs and r_coefs are temporary variables + * to hold LR decomposition results. */ + void FilterDataArray(std::vector &outs, const std::vector &data, + std::vector &diffs, std::vector &scratch, + std::vector &l_coefs, std::vector &m_coefs, + std::vector &r_coefs); + + template + void + InitializeLeftSideCoefs(T1 &outL0, T1 &outM0, T1 &outR0, + const T2 &delta, const T1 &diffs0, + const T1 &diffs1) + { + outL0 = 0; + outM0 = 1.0 + 2 * delta * diffs0 / m_SquareGradientDelta; + outR0 = - delta * (5 * diffs0 - diffs1) / (4 * m_SquareGradientDelta); + } + + template + void + InitializeLeftSideCoefs(itk::Vector &outL0, + itk::Vector &outM0, + itk::Vector &outR0, + const T2 &delta, const itk::Vector &diffs0, + const itk::Vector &diffs1) + { + for (unsigned int i = 0;i < TDimension;++i) + { + outL0[i] = 0; + outM0[i] = 1.0 + 2 * delta * diffs0[i] / m_SquareGradientDelta; + outR0[i] = - delta * (5 * diffs0[i] - diffs1[i]) / (4 * m_SquareGradientDelta); + } + } + + template + void + InitializeIthCoefs(T1 &outLi, T1 &outMi, T1 &outRi, const T2 &delta, + const T1 &inPrevM, const T1 &inPrevR, + const T1 &diffsPrev, const T1 &diffs, + const T1 &diffsNext) + { + outLi = 1.0 / inPrevM; + outLi *= - delta * (diffs + (diffsNext - diffsPrev) / 4.0) / m_SquareGradientDelta; + + outMi = 1.0 + 2 * delta * diffs / m_SquareGradientDelta; + outMi -= outLi * inPrevR; + + outRi = - delta * (diffs - (diffsNext - diffsPrev) / 4.0) / m_SquareGradientDelta; + } + + template + void + InitializeIthCoefs(itk::Vector &outLi, + itk::Vector &outMi, + itk::Vector &outRi, const T2 &delta, + const itk::Vector &inPrevM, + const itk::Vector &inPrevR, + const itk::Vector &diffsPrev, + const itk::Vector &diffs, + const itk::Vector &diffsNext) + { + for (unsigned int i = 0;i < TDimension;++i) + { + outLi[i] = 1.0 / inPrevM[i]; + outLi[i] *= - delta * (diffs[i] + (diffsNext[i] - diffsPrev[i]) / 4.0) / m_SquareGradientDelta; + + outMi[i] = 1.0 + 2 * delta * diffs[i] / m_SquareGradientDelta; + outMi[i] -= outLi[i] * inPrevR[i]; + + outRi[i] = - delta * (diffs[i] - (diffsNext[i] - diffsPrev[i]) / 4.0) / m_SquareGradientDelta; + } + } + + template + void + InitializeRightSideCoefs(T1 &outL, T1 &outM, T1 &outR, + const T2 &delta, const T1 &inPrevM, const T1 &inPrevR, + const T1 &diffsPrev, const T1 &diffs) + { + outL = 1.0 / inPrevM; + outL *= - delta * (5 * diffs - diffsPrev) / (4.0 * m_SquareGradientDelta); + + outM = 1 + 2 * delta * diffs / m_SquareGradientDelta; + outM -= outL * inPrevR; + + outR = 0; + } + + template + void + InitializeRightSideCoefs(itk::Vector &outL, + itk::Vector &outM, + itk::Vector &outR, + const T2 &delta, const itk::Vector &inPrevM, + const itk::Vector &inPrevR, + const itk::Vector &diffsPrev, + const itk::Vector &diffs) + { + for (unsigned int i = 0;i < TDimension;++i) + { + outL[i] = 1.0 / inPrevM[i]; + outL[i] *= - delta * (5 * diffs[i] - diffsPrev[i]) / (4.0 * m_SquareGradientDelta); + + outM[i] = 1 + 2 * delta * diffs[i] / m_SquareGradientDelta; + outM[i] -= outL[i] * inPrevR[i]; + + outR[i] = 0; + } + } + + template + void + ComputeForwardBacwardSubstitution(std::vector &outs, + std::vector &scratch, const std::vector &data, + const std::vector &l_coefs, const std::vector &m_coefs, + const std::vector &r_coefs) + { + unsigned int ln = data.size(); + + // compute forward substitution inside scratch + scratch[0] = data[0]; + for (unsigned int i = 1;i < ln;++i) + scratch[i] = data[i] - l_coefs[i] * scratch[i-1]; + + // And compute backward substitution inside outs + outs[ln-1] = scratch[ln-1] / m_coefs[ln-1]; + + for (int i = ln-2;i >= 0;i--) + outs[i] = (scratch[i] - r_coefs[i] * outs[i+1]) / m_coefs[i]; + } + + + template + void + ComputeForwardBacwardSubstitution(std::vector > &outs, + std::vector > &scratch, + const std::vector > &data, + const std::vector > &l_coefs, + const std::vector > &m_coefs, + const std::vector > &r_coefs) + { + unsigned int ln = data.size(); + + // compute forward substitution inside scratch + for (unsigned int j = 0;j < TDimension;++j) + scratch[0][j] = data[0][j]; + + for (unsigned int i = 1;i < ln;++i) + { + for (unsigned int j = 0;j < TDimension;++j) + scratch[i][j] = data[i][j] - l_coefs[i][j] * scratch[i-1][j]; + } + + // And compute backward substitution inside outs + for (unsigned int j = 0;j < TDimension;++j) + outs[ln-1][j] = scratch[ln-1][j] / m_coefs[ln-1][j]; + + for (int i = ln-2;i >= 0;i--) + { + for (unsigned int j = 0;j < TDimension;++j) + outs[i][j] = (scratch[i][j] - r_coefs[i][j] * outs[i+1][j]) / m_coefs[i][j]; + } + } + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(InhomogeneousAOSLineDiffusionImageFilter); + + /** Direction in which the filter is to be applied + * this should be in the range [0,ImageDimension-1]. */ + unsigned int m_Direction; + + DiffusionScalarsImagePointer m_DiffusionScalarsImage; + double m_TimeStep; + double m_SquareGradientDelta; +}; + +} // end namespace anima + +#include "animaInhomogeneousAOSLineDiffusionImageFilter.hxx" diff --git a/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.hxx b/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.hxx new file mode 100644 index 000000000..8cf5cf56b --- /dev/null +++ b/Anima/filtering/regularization/animaInhomogeneousAOSLineDiffusionImageFilter.hxx @@ -0,0 +1,233 @@ +#pragma once + +#include "animaInhomogeneousAOSLineDiffusionImageFilter.h" +#include +#include +#include +#include + + +namespace anima +{ + +template +InhomogeneousAOSLineDiffusionImageFilter +::InhomogeneousAOSLineDiffusionImageFilter() +{ + m_Direction = 0; + this->SetNumberOfRequiredOutputs(1); + this->SetNumberOfRequiredInputs(1); + + m_TimeStep = 1; + m_SquareGradientDelta = 0.1; +} + +/** + * Set Input Image + */ +template +void +InhomogeneousAOSLineDiffusionImageFilter +::SetInputImage( const TInputImage * input ) +{ + // ProcessObject is not const_correct so this const_cast is required + itk::ProcessObject::SetNthInput(0,const_cast< TInputImage * >(input) ); +} + +/** + * Get Input Image + */ +template +const TInputImage * +InhomogeneousAOSLineDiffusionImageFilter +::GetInputImage() +{ + return dynamic_cast(itk::ProcessObject::GetInput(0)); +} + +/** + * Apply Recursive Filter + */ +template +void +InhomogeneousAOSLineDiffusionImageFilter +::FilterDataArray(std::vector &outs, const std::vector &data, + std::vector &diffs, std::vector &scratch, + std::vector &l_coefs, std::vector &m_coefs, + std::vector &r_coefs) +{ + // Initialize m_0 and r_0 + double delta = m_TimeStep * TInputImage::ImageDimension; + + this->InitializeLeftSideCoefs(l_coefs[0],m_coefs[0],r_coefs[0], + delta,diffs[0],diffs[1]); + + // Computing l, m and r coefs + unsigned int ln = data.size(); + + for (unsigned int i = 1;i < ln - 1;++i) + { + this->InitializeIthCoefs(l_coefs[i],m_coefs[i],r_coefs[i],delta,m_coefs[i-1],r_coefs[i-1],diffs[i-1],diffs[i],diffs[i+1]); + } + + // Compute last l, m, r coefs + this->InitializeRightSideCoefs(l_coefs[ln-1],m_coefs[ln-1],r_coefs[ln-1], + delta,m_coefs[ln-2],r_coefs[ln-2],diffs[ln-2],diffs[ln-1]); + + // Now compute forward substitution inside scratch + this->ComputeForwardBacwardSubstitution(outs,scratch,data,l_coefs,m_coefs,r_coefs); +} + +// +// we need all of the image in just the "Direction" we are separated into +// +template +void +InhomogeneousAOSLineDiffusionImageFilter +::EnlargeOutputRequestedRegion(itk::DataObject *output) +{ + TOutputImage *out = dynamic_cast(output); + + if (out) + { + OutputImageRegionType outputRegion = out->GetRequestedRegion(); + const OutputImageRegionType &largestOutputRegion = out->GetLargestPossibleRegion(); + + // verify sane parameter + if (this->m_Direction >= outputRegion.GetImageDimension()) + { + itkExceptionMacro("Direction selected for filtering is greater than ImageDimension") + } + + // expand output region to match largest in the "Direction" dimension + outputRegion.SetIndex(m_Direction, largestOutputRegion.GetIndex(m_Direction)); + outputRegion.SetSize(m_Direction, largestOutputRegion.GetSize(m_Direction)); + + out->SetRequestedRegion(outputRegion); + } +} + +template +void +InhomogeneousAOSLineDiffusionImageFilter +::BeforeThreadedGenerateData() +{ + typename TInputImage::ConstPointer inputImage(this->GetInputImage()); + + const unsigned int imageDimension = inputImage->GetImageDimension(); + + if( this->m_Direction >= imageDimension ) + { + itkExceptionMacro("Direction selected for filtering is greater than ImageDimension"); + } + + m_SquareGradientDelta = (inputImage->GetSpacing()[m_Direction]) * (inputImage->GetSpacing()[m_Direction]); +} + +template +void +InhomogeneousAOSLineDiffusionImageFilter +::GenerateData() +{ + this->AllocateOutputs(); + this->BeforeThreadedGenerateData(); + + using RegionType = itk::ImageRegion ; + typename TOutputImage::Pointer outputImage(this->GetOutput()); + const RegionType region = outputImage->GetRequestedRegion(); + + this->GetMultiThreader()->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + this->GetMultiThreader()->template ParallelizeImageRegionRestrictDirection( + this->m_Direction, region, [this](const RegionType & lambdaRegion) { this->DynamicThreadedGenerateData(lambdaRegion); }, this); +} + +/** + * Compute filter + * line by line in one of the dimensions + */ +template +void +InhomogeneousAOSLineDiffusionImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType& outputRegionForThread) +{ + typedef typename TOutputImage::PixelType OutputPixelType; + + typedef itk::ImageLinearConstIteratorWithIndex< TInputImage > InputConstIteratorType; + typedef itk::ImageLinearConstIteratorWithIndex< TDiffusionScalarImage > DiffusionScalarConstIteratorType; + typedef itk::ImageLinearIteratorWithIndex< TOutputImage > OutputIteratorType; + + typedef itk::ImageRegion< TInputImage::ImageDimension > RegionType; + + typename TInputImage::ConstPointer inputImage(this->GetInputImage ()); + typename TOutputImage::Pointer outputImage(this->GetOutput()); + + RegionType region = outputRegionForThread; + + InputConstIteratorType inputIterator(inputImage, region); + DiffusionScalarConstIteratorType diffScalarsIterator(m_DiffusionScalarsImage, region); + OutputIteratorType outputIterator(outputImage, region); + + inputIterator.SetDirection(this->m_Direction); + diffScalarsIterator.SetDirection(this->m_Direction); + outputIterator.SetDirection(this->m_Direction); + + const unsigned int ln = region.GetSize()[this->m_Direction]; + + std::vector inps(ln); + std::vector diffs(ln); + std::vector outs(ln); + std::vector scratch(ln); + std::vector l_coefs(ln); + std::vector m_coefs(ln); + std::vector r_coefs(ln); + + try // this try is intended to catch an eventual AbortException. + { + inputIterator.GoToBegin(); + diffScalarsIterator.GoToBegin(); + outputIterator.GoToBegin(); + + while(!inputIterator.IsAtEnd()) + { + unsigned int i = 0; + while(!inputIterator.IsAtEndOfLine()) + { + diffs[i] = diffScalarsIterator.Get(); + inps[i++] = inputIterator.Get(); + + ++inputIterator; + ++diffScalarsIterator; + } + + this->FilterDataArray(outs, inps, diffs, scratch, l_coefs, m_coefs, r_coefs); + + unsigned int j = 0; + while(!outputIterator.IsAtEndOfLine()) + { + outputIterator.Set(static_cast(outs[j++])); + ++outputIterator; + } + + inputIterator.NextLine(); + diffScalarsIterator.NextLine(); + outputIterator.NextLine(); + } + } + catch(itk::ProcessAborted &) + { + throw; + } +} + +template +void +InhomogeneousAOSLineDiffusionImageFilter +::PrintSelf(std::ostream& os, itk::Indent indent) const +{ + Superclass::PrintSelf(os,indent); + + os << indent << "Direction: " << m_Direction << std::endl; + os << "Time step: " << m_TimeStep << std::endl; +} + +} // end namespace anima diff --git a/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.h b/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.h new file mode 100644 index 000000000..e452fed87 --- /dev/null +++ b/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace anima +{ + +template +class InhomogeneousDiffusionImageFilter : + public itk::ImageToImageFilter +{ +public: + /** Standard class typedefs. */ + typedef InhomogeneousDiffusionImageFilter Self; + typedef itk::ImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + /** Pixel Type of the input image */ + typedef TInputImage InputImageType; + typedef TDiffusionScalarImage DiffusionScalarsImageType; + typedef TOutputImage OutputImageType; + typedef typename TInputImage::PixelType PixelType; + typedef typename itk::NumericTraits::RealType RealType; + typedef typename itk::NumericTraits::ScalarRealType ScalarRealType; + + + /** Runtime information support. */ + itkTypeMacro(InhomogeneousDiffusionImageFilter, + ImageToImageFilter) + + /** Image dimension. */ + itkStaticConstMacro(ImageDimension, unsigned int, + TInputImage::ImageDimension); + + /** Define the image type for internal computations + RealType is usually 'double' in NumericTraits. + Here we prefer double in order to save memory. */ + + typedef typename itk::NumericTraits< PixelType >::RealType InternalRealType; + typedef typename InputImageType::template Rebind::Type RealImageType; + + /** The first in the pipeline */ + typedef anima::InhomogeneousAOSLineDiffusionImageFilter < + RealImageType, + DiffusionScalarsImageType, + RealImageType + > InternalAOSDiffusionFilterType; + + typedef itk::CastImageFilter RealCastingFilterType; + typedef itk::CastImageFilter OutCastingFilterType; + + /** Pointer to a gaussian filter. */ + typedef typename InternalAOSDiffusionFilterType::Pointer InternalAOSDiffusionFilterPointer; + + typedef typename DiffusionScalarsImageType::Pointer DiffusionScalarsImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + itkSetMacro(NumberOfSteps, unsigned int) + itkSetMacro(StepLength, double) + itkSetMacro(DiffusionSourceFactor, double) + itkSetObjectMacro(DiffusionScalarsImage, DiffusionScalarsImageType) + +protected: + InhomogeneousDiffusionImageFilter(); + virtual ~InhomogeneousDiffusionImageFilter() {} + void PrintSelf(std::ostream& os, itk::Indent indent) const ITK_OVERRIDE; + + /** Generate Data */ + void GenerateData() ITK_OVERRIDE; + + /** InhomogeneousDiffusionImageFilter needs all of the input to produce an + * output. Therefore, InhomogeneousDiffusionImageFilter needs to provide + * an implementation for GenerateInputRequestedRegion in order to inform + * the pipeline execution model. + * \sa ImageToImageFilter::GenerateInputRequestedRegion() */ + virtual void GenerateInputRequestedRegion() ITK_OVERRIDE; + + // Override since the filter produces the entire dataset + void EnlargeOutputRequestedRegion(itk::DataObject *output) ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(InhomogeneousDiffusionImageFilter); + + DiffusionScalarsImagePointer m_DiffusionScalarsImage; + + unsigned int m_NumberOfSteps; + double m_StepLength; + double m_DiffusionSourceFactor; +}; + +} // end namespace anima + +#include "animaInhomogeneousDiffusionImageFilter.hxx" diff --git a/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.hxx b/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.hxx new file mode 100644 index 000000000..5568622bf --- /dev/null +++ b/Anima/filtering/regularization/animaInhomogeneousDiffusionImageFilter.hxx @@ -0,0 +1,174 @@ +#pragma once + +#include "animaInhomogeneousDiffusionImageFilter.h" +#include + +#include +#include +#include + +namespace anima +{ + +/** + * Constructor + */ +template +InhomogeneousDiffusionImageFilter +::InhomogeneousDiffusionImageFilter() +{ + m_StepLength = 1; + m_NumberOfSteps = 10; + m_DiffusionSourceFactor = 0; +} + +template +void +InhomogeneousDiffusionImageFilter +::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method. this should + // copy the output requested region to the input requested region + Superclass::GenerateInputRequestedRegion(); + + // This filter needs all of the input + typename InhomogeneousDiffusionImageFilter::InputImagePointer image = const_cast( this->GetInput() ); + if( image ) + { + image->SetRequestedRegion( this->GetInput()->GetLargestPossibleRegion() ); + } +} + +template +void +InhomogeneousDiffusionImageFilter +::EnlargeOutputRequestedRegion(itk::DataObject *output) +{ + TOutputImage *out = dynamic_cast(output); + + if (out) + { + out->SetRequestedRegion( out->GetLargestPossibleRegion() ); + } +} + +/** + * Compute filter for Gaussian kernel + */ +template +void +InhomogeneousDiffusionImageFilter +::GenerateData(void) +{ + itkDebugMacro(<< "InhomogeneousDiffusionImageFilter generating data "); + + typedef itk::AddImageFilter AddImageFilterType; + typedef itk::MultiplyImageFilter, RealImageType> MultiplyConstantImageFilterType; + typedef itk::DivideImageFilter, RealImageType> DivideConstantImageFilterType; + + const typename TInputImage::ConstPointer inputImage(this->GetInput()); + const typename TInputImage::RegionType region = inputImage->GetRequestedRegion(); + + typename RealCastingFilterType::Pointer inputCast = RealCastingFilterType::New(); + inputCast->SetInput(inputImage); + inputCast->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + inputCast->Update(); + + typename RealImageType::SpacingType spacing = inputImage->GetSpacing(); + + typename RealImageType::Pointer workImage = inputCast->GetOutput(); + workImage->DisconnectPipeline(); + + for (unsigned int i = 0;i < m_NumberOfSteps;++i) + { + typename RealImageType::Pointer resImage; + + for (unsigned int j = 0;j < ImageDimension;++j) + { + InternalAOSDiffusionFilterPointer internalFilter = InternalAOSDiffusionFilterType::New(); + internalFilter->SetInput(workImage); + internalFilter->SetDiffusionScalarsImage(m_DiffusionScalarsImage); + internalFilter->SetTimeStep(m_StepLength / (spacing[j] * spacing[j])); + internalFilter->SetDirection(j); + internalFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + internalFilter->Update(); + + if (j == 0) + { + resImage = internalFilter->GetOutput(); + resImage->DisconnectPipeline(); + } + else + { + typename AddImageFilterType::Pointer imageAdder = AddImageFilterType::New(); + imageAdder->SetInput(0,resImage); + imageAdder->SetInput(1,internalFilter->GetOutput()); + imageAdder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + imageAdder->Update(); + resImage = imageAdder->GetOutput(); + resImage->DisconnectPipeline(); + } + } + + typename DivideConstantImageFilterType::Pointer imageDivider = DivideConstantImageFilterType::New(); + imageDivider->SetInput(resImage); + imageDivider->SetConstant(ImageDimension); + imageDivider->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + imageDivider->Update(); + + if (m_DiffusionSourceFactor > 0) + { + typename MultiplyConstantImageFilterType::Pointer imageMultiplier = MultiplyConstantImageFilterType::New(); + imageMultiplier->SetInput(workImage); + imageMultiplier->SetConstant(m_DiffusionSourceFactor * m_StepLength); + imageMultiplier->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + imageMultiplier->Update(); + + typename AddImageFilterType::Pointer finalImageAdder = AddImageFilterType::New(); + finalImageAdder->SetInput(0,imageDivider->GetOutput()); + finalImageAdder->SetInput(1,imageMultiplier->GetOutput()); + finalImageAdder->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + finalImageAdder->Update(); + + workImage = finalImageAdder->GetOutput(); + workImage->DisconnectPipeline(); + } + else + { + workImage = imageDivider->GetOutput(); + } + } + + typename OutCastingFilterType::Pointer outCastFilter = OutCastingFilterType::New(); + outCastFilter->SetInput(workImage); + outCastFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + // graft our output to the internal filter to force the proper regions + // to be generated + outCastFilter->GraftOutput(this->GetOutput()); + outCastFilter->Update(); + + this->GraftOutput(outCastFilter->GetOutput()); +} + + +template +void +InhomogeneousDiffusionImageFilter +::PrintSelf(std::ostream& os, itk::Indent indent) const +{ + Superclass::PrintSelf(os,indent); + + os << "Number of diffusion steps: " << m_NumberOfSteps << std::endl; + os << "Diffusion source factor: " << m_DiffusionSourceFactor << std::endl; + os << "Step length: " << m_StepLength << std::endl; +} + + +} // end namespace anima From 31f036f5d9aeee48b92b326f7f49193c410f84be Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 14:43:19 +0200 Subject: [PATCH 09/17] fusion of segmentation --- Anima/segmentation/CMakeLists.txt | 3 + Anima/segmentation/commands/CMakeLists.txt | 2 + .../collapse_label_image/CMakeLists.txt | 36 + .../animaCollapseLabelImage.cxx | 68 ++ .../CMakeLists.txt | 36 + .../animaConnectedComponentsMetrics.cxx | 198 ++++ .../animaConnectedComponentsMetricsFilter.h | 349 +++++++ .../animaConnectedComponentsMetricsFilter.hxx | 719 +++++++++++++ .../animaConnectedComponentsVolumeFilter.h | 127 +++ .../animaConnectedComponentsVolumeFilter.hxx | 141 +++ .../lesion-simulation/CMakeLists.txt | 3 + .../animaPickLesionSeedImageFilter.h | 64 ++ .../animaPickLesionSeedImageFilter.hxx | 96 ++ .../grow_lesion_seeds/CMakeLists.txt | 36 + .../animaGrowLesionSeeds.cxx | 67 ++ .../pick_lesion_seeds/CMakeLists.txt | 36 + .../animaPickLesionSeeds.cxx | 60 ++ .../qmri_lesion_sample_creator/CMakeLists.txt | 36 + .../animaQMRILesionSampleCreator.cxx | 134 +++ .../animaQMRISampleCreationImageFilter.h | 105 ++ .../animaQMRISampleCreationImageFilter.hxx | 582 +++++++++++ Anima/segmentation/staple/CMakeLists.txt | 37 + .../staple/animaMultiThreadedSTAPLE.cxx | 153 +++ .../animaMultiThreadedSTAPLEImageFilter.h | 248 +++++ .../animaMultiThreadedSTAPLEImageFilter.hxx | 947 ++++++++++++++++++ 25 files changed, 4283 insertions(+) create mode 100644 Anima/segmentation/commands/CMakeLists.txt create mode 100644 Anima/segmentation/commands/collapse_label_image/CMakeLists.txt create mode 100644 Anima/segmentation/commands/collapse_label_image/animaCollapseLabelImage.cxx create mode 100644 Anima/segmentation/commands/connected_components_metrics/CMakeLists.txt create mode 100644 Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetrics.cxx create mode 100644 Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.h create mode 100644 Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.hxx create mode 100644 Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.h create mode 100644 Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.hxx create mode 100644 Anima/segmentation/lesion-simulation/CMakeLists.txt create mode 100644 Anima/segmentation/lesion-simulation/animaPickLesionSeedImageFilter.h create mode 100644 Anima/segmentation/lesion-simulation/animaPickLesionSeedImageFilter.hxx create mode 100644 Anima/segmentation/lesion-simulation/grow_lesion_seeds/CMakeLists.txt create mode 100644 Anima/segmentation/lesion-simulation/grow_lesion_seeds/animaGrowLesionSeeds.cxx create mode 100644 Anima/segmentation/lesion-simulation/pick_lesion_seeds/CMakeLists.txt create mode 100644 Anima/segmentation/lesion-simulation/pick_lesion_seeds/animaPickLesionSeeds.cxx create mode 100644 Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt create mode 100644 Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx create mode 100644 Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h create mode 100644 Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx create mode 100644 Anima/segmentation/staple/CMakeLists.txt create mode 100644 Anima/segmentation/staple/animaMultiThreadedSTAPLE.cxx create mode 100644 Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.h create mode 100644 Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.hxx diff --git a/Anima/segmentation/CMakeLists.txt b/Anima/segmentation/CMakeLists.txt index b23bd807e..89c459344 100644 --- a/Anima/segmentation/CMakeLists.txt +++ b/Anima/segmentation/CMakeLists.txt @@ -5,7 +5,10 @@ project(ANIMA-SEGMENTATION) ################################################################################ add_subdirectory(basic_tools) +add_subdirectory(commands) add_subdirectory(graph_cut) add_subdirectory(isosurface) +add_subdirectory(lesion-simulation) +add_subdirectory(staple) add_subdirectory(tissues_em_classification) add_subdirectory(validation_tools) diff --git a/Anima/segmentation/commands/CMakeLists.txt b/Anima/segmentation/commands/CMakeLists.txt new file mode 100644 index 000000000..1f3b701fe --- /dev/null +++ b/Anima/segmentation/commands/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(collapse_label_image) +add_subdirectory(connected_components_metrics) diff --git a/Anima/segmentation/commands/collapse_label_image/CMakeLists.txt b/Anima/segmentation/commands/collapse_label_image/CMakeLists.txt new file mode 100644 index 000000000..879d66949 --- /dev/null +++ b/Anima/segmentation/commands/collapse_label_image/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaCollapseLabelImage) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/commands/collapse_label_image/animaCollapseLabelImage.cxx b/Anima/segmentation/commands/collapse_label_image/animaCollapseLabelImage.cxx new file mode 100644 index 000000000..40bf01b91 --- /dev/null +++ b/Anima/segmentation/commands/collapse_label_image/animaCollapseLabelImage.cxx @@ -0,0 +1,68 @@ +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("Collapses all labels of a label image so that they are contiguous.\n INRIA / IRISA - VisAGeS/Empenn Team", ' ', ANIMA_VERSION); + + TCLAP::ValueArg inArg("i","inputfile","Input image",true,"","input image",cmd); + TCLAP::ValueArg outArg("o","outputfile","Output image",true,"","output image",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image ImageType; + + ImageType::Pointer inputImage = anima::readImage (inArg.getValue()); + + itk::ImageRegionIterator inputItr(inputImage,inputImage->GetLargestPossibleRegion()); + std::vector usefulLabels; + + while (!inputItr.IsAtEnd()) + { + if (inputItr.Get() != 0) + { + bool isAlreadyIn = false; + for (unsigned int i = 0;i < usefulLabels.size();++i) + { + if (inputItr.Get() == usefulLabels[i]) + { + isAlreadyIn = true; + break; + } + } + + if (!isAlreadyIn) + usefulLabels.push_back(inputItr.Get()); + } + + ++inputItr; + } + + std::sort(usefulLabels.begin(),usefulLabels.end()); + + std::map labelBackMap; + + for (unsigned int i = 0;i < usefulLabels.size();++i) + labelBackMap[usefulLabels[i]] = i+1; + + inputItr.GoToBegin(); + while (!inputItr.IsAtEnd()) + { + if (inputItr.Get() != 0) + inputItr.Set(labelBackMap[inputItr.Get()]); + + ++inputItr; + } + + anima::writeImage (outArg.getValue(),inputImage); + + return EXIT_SUCCESS; +} diff --git a/Anima/segmentation/commands/connected_components_metrics/CMakeLists.txt b/Anima/segmentation/commands/connected_components_metrics/CMakeLists.txt new file mode 100644 index 000000000..958d7c523 --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaConnectedComponentsMetrics) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetrics.cxx b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetrics.cxx new file mode 100644 index 000000000..8a0dd7795 --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetrics.cxx @@ -0,0 +1,198 @@ +#include + +#include +#include +#include "animaConnectedComponentsMetricsFilter.h" + + +struct arguments +{ + std::string ref, test, outputCCref, outputCCtest, outputDiffVoxel, outputDiffRef, outputDiffTest, + outputEvolutionRef, outputEvolutionTest, outputTextVolRef, outputTextVolTest, outputTextDiff; + unsigned int pthread; + double min, alpha, beta; + bool full, verbose, detection; +}; + +template +void +connectedComponent(const arguments &args) +{ + typedef itk::Image ImageType; + + // Create instance of metrics 3D filter + typedef anima::ConnectedComponentsMetricsFilter ConnectedComponentsMetricsFilterType; + typename ConnectedComponentsMetricsFilterType::Pointer ConnectedComponentsMetricsFilter = ConnectedComponentsMetricsFilterType::New(); + + // Open Images + ConnectedComponentsMetricsFilter->SetInputReference( anima::readImage( args.ref ) ); + ConnectedComponentsMetricsFilter->SetInputTest( anima::readImage( args.test ) ); + + ConnectedComponentsMetricsFilter->SetOverlapDetectionType( args.detection ); + ConnectedComponentsMetricsFilter->SetAlpha( args.alpha ); + ConnectedComponentsMetricsFilter->SetBeta( args.beta ); + ConnectedComponentsMetricsFilter->SetMinSizeMM3( args.min ); + ConnectedComponentsMetricsFilter->SetFullyConnected( args.full ); + ConnectedComponentsMetricsFilter->SetVerbose( args.verbose ); + ConnectedComponentsMetricsFilter->SetDimension( Dimension ); + if(args.pthread > 0) + ConnectedComponentsMetricsFilter->SetNumberOfWorkUnits(args.pthread); + + ConnectedComponentsMetricsFilter->SetOutputFilenameCCRef( args.outputCCref ); + ConnectedComponentsMetricsFilter->SetOutputFilenameCCTest( args.outputCCtest ); + + ConnectedComponentsMetricsFilter->SetOutputFilenameDiffVoxelWise( args.outputDiffVoxel ); + ConnectedComponentsMetricsFilter->SetOutputFilenameDiffTest( args.outputDiffTest ); + ConnectedComponentsMetricsFilter->SetOutputFilenameDiffRef( args.outputDiffRef ); + + ConnectedComponentsMetricsFilter->SetOutputFilenameTextVolTest( args.outputTextVolTest ); + ConnectedComponentsMetricsFilter->SetOutputFilenameTextVolRef( args.outputTextVolRef ); + ConnectedComponentsMetricsFilter->SetOutputFilenameTextMetrics( args.outputTextDiff ); + + ConnectedComponentsMetricsFilter->SetOutputFilenameEvolutionTest( args.outputEvolutionTest ); + ConnectedComponentsMetricsFilter->SetOutputFilenameEvolutionRef( args.outputEvolutionRef ); + + // Process + itk::TimeProbe timer; + + timer.Start(); + + ConnectedComponentsMetricsFilter->Update(); + ConnectedComponentsMetricsFilter->WriteOutputs(); + timer.Stop(); + + std::cout << "Elapsed Time: " << timer.GetTotal() << timer.GetUnit() << std::endl; +} + +void +retrieveDimension(const arguments &args, itk::ImageIOBase::Pointer imageIO) +{ + unsigned int nbDim = imageIO->GetNumberOfDimensions(); + + switch(nbDim) + { + case 2: + connectedComponent<2>(args); + break; + case 3: + connectedComponent<3>(args); + break; + case 4: + connectedComponent<4>(args); + break; + default: + itk::ExceptionObject excp(__FILE__, __LINE__, "Number of dimension not supported.", ITK_LOCATION); + throw excp; + } +} + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',"1.0"); + + TCLAP::ValueArg refArg("r","ref","Reference input image",true,"","input reference image",cmd); + TCLAP::ValueArg testArg("t","test","Test input image",true,"","input test image",cmd); + + TCLAP::ValueArg outCCRefArg("","output-cc-ref","Output connected components for reference image",false,"","reference connected components",cmd); + TCLAP::ValueArg outCCTestArg("","output-cc-test","Output connected components for test image",false,"","reference connected components",cmd); + TCLAP::ValueArg outDiffVoxelArg("","output-diff-voxel","Output difference image voxel wise",false,"","voxel wise diff",cmd); + TCLAP::ValueArg outDiffRefArg("","output-diff-ref","Output difference object wise for reference image",false,"","object wise diff",cmd); + TCLAP::ValueArg outDiffTestArg("","output-diff-test","Output difference object wise for test image",false,"","object wise diff",cmd); + TCLAP::ValueArg outEvolutionRefArg("","output-evol-ref","Output evolution for reference image",false,"","evolution reference",cmd); + TCLAP::ValueArg outEvolutionTestArg("","output-evol-test","Output evolution for test image",false,"","evolution test",cmd); + + TCLAP::ValueArg outTextVolRefArg("","output-vol-ref","Output text file containing reference image volume information",false,"","volume reference information",cmd); + TCLAP::ValueArg outTextVolTestArg("","output-vol-test","Output text file containing test image volume information",false,"","volume test information",cmd); + TCLAP::ValueArg outTextDiffArg("","output-metrics","Output text file containing metrics results",false,"","metrics information",cmd); + + TCLAP::ValueArg alphaArg("a","alpha","overlap factor",false,0,"overlap factor",cmd); + TCLAP::ValueArg betaArg("b","beta","size evolution factor",false,0,"size evolution factor",cmd); + TCLAP::ValueArg minSizeArg("m","minsize","minimal component size in mm3",false,0,"minimal component size",cmd); + TCLAP::SwitchArg fullConnectArg("F","full-connect","Use 26-connectivity instead of 6-connectivity",cmd,false); + TCLAP::SwitchArg overlapDetectionArg("d","overlap-detect","use overlap detection type",cmd,false); + TCLAP::SwitchArg verboseArg("v","verbose","print information",cmd,false); + TCLAP::ValueArg numThreadsArg("T","threads","Number of execution threads (default: 0 = all cores)",false,0,"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; + } + + // Find out the type of the image in reference file + itk::ImageIOBase::Pointer imageIOref = itk::ImageIOFactory::CreateImageIO(refArg.getValue().c_str(),itk::IOFileModeEnum::ReadMode); + if( !imageIOref ) + { + std::cerr << "Itk could not find suitable IO factory for the input" << std::endl; + return EXIT_FAILURE; + } + + // Now that we found the appropriate ImageIO class, ask it to read the meta data from the image file. + imageIOref->SetFileName(refArg.getValue()); + imageIOref->ReadImageInformation(); + + + // Find out the type of the image in test file + itk::ImageIOBase::Pointer imageIOtest = itk::ImageIOFactory::CreateImageIO(testArg.getValue().c_str(),itk::IOFileModeEnum::ReadMode); + if( !imageIOtest ) + { + std::cerr << "Itk could not find suitable IO factory for the input" << std::endl; + return EXIT_FAILURE; + } + + // Now that we found the appropriate ImageIO class, ask it to read the meta data from the image file. + imageIOtest->SetFileName(testArg.getValue()); + imageIOtest->ReadImageInformation(); + + if(imageIOref->GetNumberOfDimensions() != imageIOtest->GetNumberOfDimensions()) + { + std::cerr << "Input images do not have the same sizes" << std::endl; + return EXIT_FAILURE; + } + + std::cout<<"\npreparing filter...\n"; + + arguments args; + + args.ref = refArg.getValue(); + args.test = testArg.getValue(); + + args.outputCCref = outCCRefArg.getValue(); + args.outputCCtest = outCCTestArg.getValue(); + + args.outputDiffVoxel = outDiffVoxelArg.getValue(); + args.outputDiffRef = outDiffRefArg.getValue(); + args.outputDiffTest = outDiffTestArg.getValue(); + + args.outputEvolutionRef = outEvolutionRefArg.getValue(); + args.outputEvolutionTest = outEvolutionTestArg.getValue(); + + args.outputTextVolRef = outTextVolRefArg.getValue(); + args.outputTextVolTest = outTextVolTestArg.getValue(); + args.outputTextDiff = outTextDiffArg.getValue(); + + args.detection = overlapDetectionArg.getValue(); + args.alpha = alphaArg.getValue(); + args.beta = betaArg.getValue(); + args.verbose = verboseArg.getValue(); + + args.min = minSizeArg.getValue(); + args.full= fullConnectArg.getValue(); + args.pthread = numThreadsArg.getValue(); + + try + { + retrieveDimension(args, imageIOref); + } + catch ( itk::ExceptionObject & err ) + { + std::cerr << "Itk cannot concatenate, be sure to use valid arguments..." << std::endl; + std::cerr << err << std::endl; + return EXIT_FAILURE; + } +} + diff --git a/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.h b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.h new file mode 100644 index 000000000..7573d00d1 --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.h @@ -0,0 +1,349 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace anima +{ + +/** + * @brief Compute connected components metrics. + * + */ +template +class ConnectedComponentsMetricsFilter : +public itk::ImageToImageFilter< ImageType , OutputType > +{ +public: + /** Standard class typedefs. */ + typedef ConnectedComponentsMetricsFilter Self; + typedef itk::ImageToImageFilter< ImageType , OutputType > 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(ConnectedComponentsMetricsFilter, ImageToImageFilter) + + /** Image typedef support */ + + /** Type of image. */ + typedef typename ImageType::ConstPointer ImageConstPointerType; + typedef typename itk::ImageRegionConstIterator< ImageType > ImageConstIteratorType; + + typedef typename OutputType::Pointer OutputImagePointerType; + typedef typename OutputType::PixelType OutputPixelType; + typedef typename itk::ImageRegionIterator< OutputType > OutputIteratorType; + + typedef anima::ConnectedComponentsVolumeFilter ConnectedComponentVolumeFilterType; + + typedef typename ImageType::SpacingValueType spacingValueType; + + void WriteOutputs(); + + void SetInputReference(const ImageType* image); + void SetInputTest(const ImageType* image); + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + itkSetMacro(MinSizeMM3, double) + itkGetMacro(MinSizeMM3, double) + + itkSetMacro(Beta, double) + itkGetMacro(Beta, double) + + itkSetMacro(Alpha, double) + itkGetMacro(Alpha, double) + + itkSetMacro(FullyConnected, bool) + itkGetMacro(FullyConnected, bool) + + itkSetMacro(Verbose, bool) + itkGetMacro(Verbose, bool) + + itkSetMacro(OverlapDetectionType, bool) + itkGetMacro(OverlapDetectionType, bool) + + itkSetMacro(Dimension, unsigned int) + itkGetMacro(Dimension, unsigned int) + + void SetOutputFilenameCCRef(std::string outputFilename) {m_OutputCCReference_filename=outputFilename;} + void SetOutputFilenameCCTest(std::string outputFilename) {m_OutputCCTest_filename=outputFilename;} + + void SetOutputFilenameDiffVoxelWise(std::string outputFilename) {m_OutputDiffVoxelWise_filename=outputFilename;} + void SetOutputFilenameDiffTest(std::string outputFilename) {m_OutputDiffTest_filename=outputFilename;} + void SetOutputFilenameDiffRef(std::string outputFilename) {m_OutputDiffRef_filename=outputFilename;} + + void SetOutputFilenameEvolutionTest(std::string outputFilename) {m_OutputEvolutionTest_filename=outputFilename;} + void SetOutputFilenameEvolutionRef(std::string outputFilename) {m_OutputEvolutionRef_filename=outputFilename;} + + void SetOutputFilenameTextVolTest(std::string outputFilename) {m_volume_test_text_filename=outputFilename;} + void SetOutputFilenameTextVolRef(std::string outputFilename) {m_volume_ref_text_filename=outputFilename;} + void SetOutputFilenameTextMetrics(std::string outputFilename) {m_metrics_text_filename=outputFilename;} + + itkGetMacro(OriginalNumberOfObjectsTest, unsigned int) + itkGetMacro(NumberOfObjectsTest, unsigned int) + itkGetMacro(TestTotalVolume, double) + + itkGetMacro(OriginalNumberOfObjectsRef, unsigned int) + itkGetMacro(NumberOfObjectsRef, unsigned int) + itkGetMacro(ReferenceTotalVolume, double) + + itkGetMacro(nb_true_positive_test, unsigned int) + itkGetMacro(nb_true_positive_ref, unsigned int) + itkGetMacro(nb_false_positive, unsigned int) + itkGetMacro(nb_false_negative, unsigned int) + + itkGetMacro(nb_grow_ref, unsigned int) + itkGetMacro(nb_shrink_ref, unsigned int) + itkGetMacro(nb_same_ref, unsigned int) + + itkGetMacro(nb_grow_test, unsigned int) + itkGetMacro(nb_shrink_test, unsigned int) + itkGetMacro(nb_same_test, unsigned int) + + itkGetMacro(VolumeDifference, double) + itkGetMacro(VolumeDifferenceRatio, double) + + itkGetMacro(FalsePositiveRate, double) + itkGetMacro(FalseNegativeRate, double) + itkGetMacro(RefTruePositiveRate, double) + itkGetMacro(TestTruePositiveRate, double) + + itkGetMacro(RefGrowRate, double) + itkGetMacro(RefShrinkRate, double) + itkGetMacro(RefSameRate, double) + + itkGetMacro(TestGrowRate, double) + itkGetMacro(TestShrinkRate, double) + itkGetMacro(TestSameRate, double) + + std::vector GetGrownTest(){return m_vect_grow_test;} + std::vector GetShrinkTest(){return m_vect_shrink_test;} + std::vector GetSameTest(){return m_vect_same_test;} + + std::vector GetGrownRef(){return m_vect_grow_ref;} + std::vector GetShrinkRef(){return m_vect_shrink_ref;} + std::vector GetSameRef(){return m_vect_same_ref;} + + std::vector GetTestTP(){return m_vect_true_positive_test;} + std::vector GetRefTP(){return m_vect_true_positive_ref;} + + std::vector GetVectVolumeTest(){return m_vect_volume_test;} + std::vector GetVectVolumeReference(){return m_vect_volume_ref;} + + OutputType* GetOutputCCReference(); + OutputType* GetOutputCCTest(); + OutputType* GetOutputDiffVoxelWise(); + OutputType* GetOutputDiffTest(); + OutputType* GetOutputDiffRef(); + + OutputType* GetOutputEvolutionRef(); + OutputType* GetOutputEvolutionTest(); + +protected: + ConnectedComponentsMetricsFilter() + { + this->SetNumberOfRequiredOutputs(7); + this->SetNumberOfRequiredInputs(2); + + for(int i = 0;i < 7;++i) + this->SetNthOutput(i, this->MakeOutput(i)); + + /** + * Input parameters + * */ + m_OverlapDetectionType = false; + m_MinSizeMM3 = 0; + m_FullyConnected = false; + m_Verbose = false; + m_Beta = 1; + m_Alpha = 1; + m_SpacingTot = 1; + m_Dimension = 2; + + /** + * Images features + * */ + m_OriginalNumberOfObjectsTest = 0; + m_NumberOfObjectsTest = 0; + m_TestTotalVolume = 0; + + m_OriginalNumberOfObjectsRef = 0; + m_NumberOfObjectsRef = 0; + m_ReferenceTotalVolume = 0; + + /** + * Detection + * */ + m_nb_true_positive_test = 0; + m_nb_true_positive_ref = 0; + m_nb_false_positive = 0; + m_nb_false_negative = 0; + + /** + * Evolution + * */ + m_nb_grow_ref = 0; + m_nb_shrink_ref = 0; + m_nb_same_ref = 0; + + m_nb_grow_test = 0; + m_nb_shrink_test = 0; + m_nb_same_test = 0; + + /** + * Metrics + * */ + m_VolumeDifference = 0; + m_VolumeDifferenceRatio = std::numeric_limits::quiet_NaN(); + + m_FalsePositiveRate = std::numeric_limits::quiet_NaN(); + m_FalseNegativeRate = std::numeric_limits::quiet_NaN(); + m_RefTruePositiveRate = std::numeric_limits::quiet_NaN(); + m_TestTruePositiveRate = std::numeric_limits::quiet_NaN(); + + m_RefGrowRate = std::numeric_limits::quiet_NaN(); + m_RefShrinkRate = std::numeric_limits::quiet_NaN(); + m_RefSameRate = std::numeric_limits::quiet_NaN(); + + m_TestGrowRate = std::numeric_limits::quiet_NaN(); + m_TestShrinkRate = std::numeric_limits::quiet_NaN(); + m_TestSameRate = std::numeric_limits::quiet_NaN(); + } + + virtual ~ConnectedComponentsMetricsFilter() + { + } + + itk::DataObject::Pointer MakeOutput(unsigned int idx); + + typename ImageType::ConstPointer GetInputReference(); + typename ImageType::ConstPointer GetInputTest(); + + void Print(std::ostream& os); + void ComputeMetrics(); + void ComputeIntersectionBetweenCC(); + void ComputeEvolution(); + void ComputeDetection(); + void GenerateData() ITK_OVERRIDE; + void PrintImagesOutput(); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(ConnectedComponentsMetricsFilter); + + /** + * Input parameters + * */ + bool m_OverlapDetectionType; + double m_MinSizeMM3; + bool m_FullyConnected; + unsigned int m_Dimension; + bool m_Verbose; + double m_Beta; + double m_Alpha; + spacingValueType m_SpacingTot; + + /** + * Output filenames + * */ + + // image file + std::string m_OutputCCReference_filename; + std::string m_OutputCCTest_filename; + std::string m_OutputDiffVoxelWise_filename; + std::string m_OutputDiffRef_filename; + std::string m_OutputDiffTest_filename; + std::string m_OutputEvolutionTest_filename; + std::string m_OutputEvolutionRef_filename; + + // information text file + std::string m_volume_test_text_filename; + std::string m_volume_ref_text_filename; + std::string m_metrics_text_filename; + + /** + * Images volumes + * */ + unsigned int m_OriginalNumberOfObjectsTest; /*!< Original number of objects in test image */ + unsigned int m_NumberOfObjectsTest; /*!< Number of objects in test image (after removing small ones) */ + std::vector m_vect_volume_test; /*!< Volume of each object */ + double m_TestTotalVolume; /*!< Total volume of test image */ + + unsigned int m_OriginalNumberOfObjectsRef; /*!< Original number of objects in reference image */ + unsigned int m_NumberOfObjectsRef; /*!< Number of objects in reference image (after removing small ones) */ + std::vector m_vect_volume_ref; /*!< Volume of each object */ + double m_ReferenceTotalVolume; /*!< Total volume of reference image */ + + /** + * Interaction between connected components + * */ + std::vector > m_IntersectionVoxels; + std::vector > m_IntersectionMM3; + std::vector > m_RefInclusDansTest; + std::vector > m_TestInclusDansRef; + + /** + * Detection + * */ + std::vector m_vect_true_positive_test; /*!< List of true positive objects in test image */ + std::vector m_vect_true_positive_ref; /*!< List of true positive objects in reference image */ + std::vector m_vect_false_positive_test; /*!< List of false positive objects in test image */ + + unsigned int m_nb_true_positive_test; /*!< Number of true positive objects in test image */ + unsigned int m_nb_true_positive_ref; /*!< Number of true positive objects in reference image */ + unsigned int m_nb_false_positive; /*!< Number of false positive objects (in test image) */ + unsigned int m_nb_false_negative; /*!< Number of false negative objects (in reference image) */ + + /** + * Evolution + * */ + + unsigned int m_nb_grow_ref; /*!< Number of objects that will grow (in reference image) */ + unsigned int m_nb_shrink_ref; /*!< Number of objects that will shrink (in reference image) */ + unsigned int m_nb_same_ref; /*!< Number of objects that stay the same (in reference image) */ + + unsigned int m_nb_grow_test; /*!< Number of objects have grown (in test image) */ + unsigned int m_nb_shrink_test; /*!< Number of objects have shrinked (in test image) */ + unsigned int m_nb_same_test; /*!< Number of objects that stay the same (in test image) */ + + std::vector m_vect_grow_test; /*!< List of objects that will grow (in reference image) */ + std::vector m_vect_shrink_test; /*!< List of objects that will shrink (in reference image) */ + std::vector m_vect_same_test; /*!< List of objects that stay the same (in reference image) */ + + std::vector m_vect_grow_ref; /*!< List of objects have grown (in test image) */ + std::vector m_vect_shrink_ref; /*!< List of objects have shrinked (in test image) */ + std::vector m_vect_same_ref; /*!< List of objects that stay the same (in test image) */ + + /** + * Metrics + * */ + + double m_VolumeDifference; /*!< Volume Difference – absolute difference in volumes */ + double m_VolumeDifferenceRatio; /*!< Volume Difference Ratio – absolute difference in volumes divided by the true volume */ + + double m_FalsePositiveRate; /*!< Object wise false positive rate */ + double m_FalseNegativeRate; /*!< Object wise false negative rate */ + double m_RefTruePositiveRate; /*!< Object wise true positive rate in reference image (= recall) */ + double m_TestTruePositiveRate; /*!< Object wise true positive rate in test image (= precision) */ + + double m_RefGrowRate; /*!< Rate of growing objects in reference image */ + double m_RefShrinkRate; /*!< Rate of shrinking objects in reference image */ + double m_RefSameRate; /*!< Rate of same objects in reference image */ + + double m_TestGrowRate; /*!< Rate of growing objects in test image */ + double m_TestShrinkRate; /*!< Rate of shrinking objects in test image */ + double m_TestSameRate; /*!< Rate of same objects in test image */ +}; + +} // end of namespace anima + +#include "animaConnectedComponentsMetricsFilter.hxx" + diff --git a/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.hxx b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.hxx new file mode 100644 index 000000000..50380894e --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsMetricsFilter.hxx @@ -0,0 +1,719 @@ +#pragma once + +#include "animaConnectedComponentsMetricsFilter.h" + +namespace anima +{ +template +void ConnectedComponentsMetricsFilter::SetInputReference(const ImageType* image) +{ + this->SetNthInput(0, const_cast(image)); +} + +template +void ConnectedComponentsMetricsFilter::SetInputTest(const ImageType* image) +{ + this->SetNthInput(1, const_cast(image)); +} + +template +typename ImageType::ConstPointer ConnectedComponentsMetricsFilter::GetInputReference() +{ + return static_cast< const ImageType * > + ( this->itk::ProcessObject::GetInput(0) ); +} + +template +typename ImageType::ConstPointer ConnectedComponentsMetricsFilter::GetInputTest() +{ + return static_cast< const ImageType * > + ( this->itk::ProcessObject::GetInput(1) ); +} + +template +OutputType* ConnectedComponentsMetricsFilter::GetOutputCCReference() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(0) ); +} +template +OutputType *ConnectedComponentsMetricsFilter::GetOutputCCTest() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(1) ); +} +template +OutputType * ConnectedComponentsMetricsFilter::GetOutputDiffVoxelWise() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(2) ); +} +template +OutputType *ConnectedComponentsMetricsFilter::GetOutputDiffTest() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(3) ); +} +template +OutputType *ConnectedComponentsMetricsFilter::GetOutputDiffRef() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(4) ); +} +template +OutputType *ConnectedComponentsMetricsFilter::GetOutputEvolutionRef() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(5) ); +} +template +OutputType *ConnectedComponentsMetricsFilter::GetOutputEvolutionTest() +{ + return dynamic_cast< OutputType * >( this->itk::ProcessObject::GetOutput(6) ); +} + +template +itk::DataObject::Pointer +ConnectedComponentsMetricsFilter +::MakeOutput(unsigned int idx) +{ + itk::DataObject::Pointer output; + output = ( OutputType::New() ).GetPointer(); + return output.GetPointer(); +} + +template +void +ConnectedComponentsMetricsFilter +::WriteOutputs() +{ + if( m_OutputDiffVoxelWise_filename != "" ) + { + std::cout << "Writing output image to: " << m_OutputDiffVoxelWise_filename << std::endl; + anima::writeImage(m_OutputDiffVoxelWise_filename, this->GetOutputDiffVoxelWise() ); + } + if( m_OutputDiffTest_filename != "" ) + { + std::cout << "Writing output image to: " << m_OutputDiffTest_filename << std::endl; + anima::writeImage(m_OutputDiffTest_filename, this->GetOutputDiffTest()); + } + if( m_OutputDiffRef_filename != "" ) + { + std::cout << "Writing output image to: " << m_OutputDiffRef_filename << std::endl; + anima::writeImage(m_OutputDiffRef_filename, this->GetOutputDiffRef()); + } + if( m_OutputEvolutionTest_filename != "" ) + { + std::cout << "Writing output image to: " << m_OutputEvolutionTest_filename << std::endl; + anima::writeImage(m_OutputEvolutionTest_filename, this->GetOutputEvolutionTest()); + } + if( m_OutputEvolutionRef_filename != "" ) + { + std::cout << "Writing output image to: " << m_OutputEvolutionRef_filename << std::endl; + anima::writeImage(m_OutputEvolutionRef_filename, this->GetOutputEvolutionRef()); + } +} + +template +void +ConnectedComponentsMetricsFilter +::GenerateData() +{ + // Get information about reference image + typename ConnectedComponentVolumeFilterType::Pointer CCReferenceFilter = ConnectedComponentVolumeFilterType::New(); + CCReferenceFilter->SetInput( this->GetInputReference() ); + CCReferenceFilter->SetFullyConnected( m_FullyConnected ); + CCReferenceFilter->SetMinSizeMM3( m_MinSizeMM3 ); + CCReferenceFilter->SetOutputFilename( m_OutputCCReference_filename ); + CCReferenceFilter->SetOutputVolumeFilename( m_volume_ref_text_filename ); + CCReferenceFilter->SetDimension( m_Dimension ); + CCReferenceFilter->SetVerbose( false ); + if(this->GetNumberOfWorkUnits() > 0) + CCReferenceFilter->SetNumberOfWorkUnits( this->GetNumberOfWorkUnits() ); + CCReferenceFilter->GraftOutput( this->GetOutputCCReference() ); + CCReferenceFilter->Update(); + this->GraftNthOutput( 0 , CCReferenceFilter->GetOutput() ); + CCReferenceFilter->WriteOutputs(); + + m_OriginalNumberOfObjectsRef = CCReferenceFilter->GetOriginalNumberOfObjects(); + m_NumberOfObjectsRef = CCReferenceFilter->GetNumberOfObjects(); + m_ReferenceTotalVolume = CCReferenceFilter->GetTotalVolume(); + m_vect_volume_ref = CCReferenceFilter->GetVolumeVector(); + + // Get information about test image + typename ConnectedComponentVolumeFilterType::Pointer CCTestFilter = ConnectedComponentVolumeFilterType::New(); + CCTestFilter->SetInput( this->GetInputTest() ); + CCTestFilter->SetFullyConnected( m_FullyConnected ); + CCTestFilter->SetMinSizeMM3( m_MinSizeMM3 ); + CCTestFilter->SetOutputFilename( m_OutputCCTest_filename ); + CCTestFilter->SetOutputVolumeFilename( m_volume_test_text_filename ); + CCTestFilter->SetDimension( m_Dimension ); + CCTestFilter->SetVerbose( false ); + if(this->GetNumberOfWorkUnits() > 0) + CCTestFilter->SetNumberOfWorkUnits( this->GetNumberOfWorkUnits() ); + CCTestFilter->GraftOutput( this->GetOutputCCTest() ); + CCTestFilter->Update(); + this->GraftNthOutput( 1 , CCTestFilter->GetOutput() ); + CCTestFilter->WriteOutputs(); + + m_OriginalNumberOfObjectsTest = CCTestFilter->GetOriginalNumberOfObjects(); + m_NumberOfObjectsTest = CCTestFilter->GetNumberOfObjects(); + m_TestTotalVolume = CCTestFilter->GetTotalVolume(); + m_vect_volume_test = CCTestFilter->GetVolumeVector(); + + this->ComputeIntersectionBetweenCC(); + this->ComputeDetection(); + this->ComputeEvolution(); + this->ComputeMetrics(); + this->PrintImagesOutput(); + + if(m_metrics_text_filename.size()!=0) + { + std::ofstream fp(m_metrics_text_filename.c_str(), std::ios_base::out | std::ios_base::trunc); + if(!fp) + { + std::cerr << "cannot open output file" << m_metrics_text_filename << std::endl; + } + else + { + this->Print(fp); + fp.close(); + } + } + if(m_Verbose) + { + this->Print(std::cout); + } +} + +template +void +ConnectedComponentsMetricsFilter +::ComputeIntersectionBetweenCC() +{ + m_IntersectionVoxels.resize(m_NumberOfObjectsRef+1, std::vector (m_NumberOfObjectsTest+1,0)); + m_IntersectionMM3.resize(m_NumberOfObjectsRef+1, std::vector (m_NumberOfObjectsTest+1,0)); + m_RefInclusDansTest.resize(m_NumberOfObjectsRef+1, std::vector (m_NumberOfObjectsTest+1,0)); + m_TestInclusDansRef.resize(m_NumberOfObjectsRef+1, std::vector (m_NumberOfObjectsTest+1,0)); + + // Compute number of intersection voxels between each connected components + OutputIteratorType ccReferenceImageIt(this->GetOutputCCReference(), this->GetOutputCCReference()->GetLargestPossibleRegion()); + OutputIteratorType ccTestImageIt(this->GetOutputCCTest(), this->GetOutputCCTest()->GetLargestPossibleRegion()); + while(!ccReferenceImageIt.IsAtEnd()) + { + if((ccReferenceImageIt.Get()!=0) && (ccTestImageIt.Get()!=0)) + { + (m_IntersectionVoxels[ccReferenceImageIt.Get()][ccTestImageIt.Get()])++; + } + ++ccReferenceImageIt; + ++ccTestImageIt; + } + + // Compute intersection between each connected components in mm3 + // Identify which connected components intersect enough (according to alpha ratio) with others. + for (unsigned int i = 1; i < m_NumberOfObjectsRef+1; ++i) + { + for (unsigned int j = 1; j < m_NumberOfObjectsTest+1; ++j) + { + m_IntersectionMM3[i][j] = static_cast(m_IntersectionVoxels[i][j]) * m_SpacingTot; + if( ( m_IntersectionMM3[i][j] / m_vect_volume_test[j] ) > m_Alpha) + { + m_TestInclusDansRef[i][j] = 1; + } + if( ( m_IntersectionMM3[i][j] / m_vect_volume_ref[i] ) > m_Alpha) + { + m_RefInclusDansTest[i][j] = 1; + } + } + } +} + +template +void +ConnectedComponentsMetricsFilter +::ComputeDetection() +{ + m_vect_true_positive_ref.resize(m_NumberOfObjectsRef+1,0); + m_vect_true_positive_test.resize(m_NumberOfObjectsTest+1,0); + m_vect_false_positive_test.resize(m_NumberOfObjectsTest+1,0); + + for(unsigned int i = 1; i < m_NumberOfObjectsRef+1; i++) + { + for(unsigned int j = 1; j < m_NumberOfObjectsTest+1; j++) + { + if(m_OverlapDetectionType) + { + if(m_TestInclusDansRef[i][j] || m_RefInclusDansTest[i][j]) + { + m_vect_true_positive_ref[i] = 1; + m_vect_true_positive_test[j] = 1; + } + } + else + { + if(m_IntersectionVoxels[i][j]) + { + m_vect_true_positive_ref[i] = 1; + m_vect_true_positive_test[j] = 1; + } + } + } + } + + m_nb_true_positive_test = 0; + m_nb_true_positive_ref = 0; + m_nb_false_positive = 0; + m_nb_false_negative = 0; + + for(unsigned int i = 1; i < m_NumberOfObjectsRef+1; i++) + { + if(m_vect_true_positive_ref[i]) + { + m_nb_true_positive_ref++; + } + else + { + m_nb_false_negative++; + } + } + + for(unsigned int j = 1; j < m_NumberOfObjectsTest+1; j++) + { + if(m_vect_true_positive_test[j]) + { + m_nb_true_positive_test++; + } + else + { + m_nb_false_positive++; + m_vect_false_positive_test[j] = 1; + } + } +} + + +template +void +ConnectedComponentsMetricsFilter +::ComputeEvolution() +{ + std::vector ref_asso_vol(m_NumberOfObjectsRef+1,0); + m_vect_shrink_ref.resize(m_NumberOfObjectsRef+1,0); + m_vect_grow_ref.resize(m_NumberOfObjectsRef+1,0); + m_vect_same_ref.resize(m_NumberOfObjectsRef+1,0); + + for(unsigned int i = 1; i < m_NumberOfObjectsRef+1; i++) + { + for(unsigned int j = 1; j < m_NumberOfObjectsTest+1; j++) + { + if(m_OverlapDetectionType) + { + if(m_TestInclusDansRef[i][j] || m_RefInclusDansTest[i][j]) + { + ref_asso_vol[i] += m_vect_volume_test[j]; + } + } + else + { + if(m_IntersectionVoxels[i][j]) + { + ref_asso_vol[i] += m_vect_volume_test[j]; + } + } + } + + if(ref_asso_vol[i]!=0) + { + if(m_vect_volume_ref[i]*(1+m_Beta) <= ref_asso_vol[i]) + { + m_vect_grow_ref[i] = 1; + } + else if(m_vect_volume_ref[i]*(1-m_Beta) >= ref_asso_vol[i]) + { + m_vect_shrink_ref[i] = 1; + } + else + { + m_vect_same_ref[i] = 1; + } + } + } + + for(unsigned int i = 1; i < m_NumberOfObjectsRef+1; i++) + { + if(m_vect_grow_ref[i]) + { + m_nb_grow_ref++; + } + if(m_vect_shrink_ref[i]) + { + m_nb_shrink_ref++; + } + if(m_vect_same_ref[i]) + { + m_nb_same_ref++; + } + } + + std::vector test_asso_vol(m_NumberOfObjectsTest+1,0); + m_vect_shrink_test.resize(m_NumberOfObjectsTest+1,0); + m_vect_grow_test.resize(m_NumberOfObjectsTest+1,0); + m_vect_same_test.resize(m_NumberOfObjectsTest+1,0); + + for(unsigned int j = 1; j < m_NumberOfObjectsTest+1; j++) + { + for(unsigned int i = 1; i < m_NumberOfObjectsRef+1; i++) + { + if(m_OverlapDetectionType) + { + if(m_TestInclusDansRef[i][j] || m_RefInclusDansTest[i][j]) + { + test_asso_vol[j] += m_vect_volume_ref[i]; + } + + } + else + { + if(m_IntersectionVoxels[i][j]) + { + test_asso_vol[j] += m_vect_volume_ref[i]; + } + } + } + + if(test_asso_vol[j]!=0) + { + if(m_vect_volume_test[j] >= test_asso_vol[j]*(1+m_Beta)) + { + m_vect_grow_test[j] = 1; + } + else if(m_vect_volume_test[j] <= test_asso_vol[j]*(1-m_Beta)) + { + m_vect_shrink_test[j] = 1; + } + else + { + m_vect_same_test[j] = 1; + } + } + } + + + for(unsigned int j = 1; j < m_NumberOfObjectsTest+1; j++) + { + if(m_vect_grow_test[j]) + { + m_nb_grow_test++; + } + if(m_vect_shrink_test[j]) + { + m_nb_shrink_test++; + } + if(m_vect_same_test[j]) + { + m_nb_same_test++; + } + } +} + +template +void +ConnectedComponentsMetricsFilter +::ComputeMetrics() +{ + // ---------- Volume metrics ---------- // + m_VolumeDifference = std::abs(m_ReferenceTotalVolume - m_TestTotalVolume); + if(m_ReferenceTotalVolume!=0) + { + m_VolumeDifferenceRatio = m_VolumeDifference / m_ReferenceTotalVolume; + } + + // ---------- Object detection metrics ---------- // + if(m_NumberOfObjectsTest!=0) + { + m_FalsePositiveRate = static_cast(m_nb_false_positive) / static_cast(m_NumberOfObjectsTest); + m_TestTruePositiveRate = static_cast(m_nb_true_positive_test) / static_cast(m_NumberOfObjectsTest); + } + if(m_NumberOfObjectsRef!=0) + { + m_FalseNegativeRate = static_cast(m_nb_false_negative) / static_cast(m_NumberOfObjectsRef); + m_RefTruePositiveRate = static_cast(m_nb_true_positive_ref) / static_cast(m_NumberOfObjectsRef); + } + + // ---------- Object evolution metrics ---------- // + if(m_NumberOfObjectsTest!=0) + { + m_TestGrowRate = static_cast(m_nb_grow_test) / static_cast(m_NumberOfObjectsTest); + m_TestShrinkRate = static_cast(m_nb_shrink_test) / static_cast(m_NumberOfObjectsTest); + m_TestSameRate = static_cast(m_nb_same_test) / static_cast(m_NumberOfObjectsTest); + } + if(m_NumberOfObjectsRef!=0) + { + m_RefGrowRate = static_cast(m_nb_grow_ref) / static_cast(m_NumberOfObjectsRef); + m_RefShrinkRate = static_cast(m_nb_shrink_ref) / static_cast(m_NumberOfObjectsRef); + m_RefSameRate = static_cast(m_nb_same_ref) / static_cast(m_NumberOfObjectsRef); + } +} + +template +void +ConnectedComponentsMetricsFilter +::PrintImagesOutput() +{ + OutputImagePointerType outputDiffVoxelWise = this->GetOutputDiffVoxelWise(); + outputDiffVoxelWise->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + outputDiffVoxelWise->CopyInformation(this->GetInput(0)); + outputDiffVoxelWise->Allocate(); + outputDiffVoxelWise->FillBuffer(0); + + OutputImagePointerType outputDiffTest = this->GetOutputDiffTest(); + outputDiffTest->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + outputDiffTest->CopyInformation(this->GetInput(0)); + outputDiffTest->Allocate(); + outputDiffTest->FillBuffer(0); + + OutputImagePointerType outputDiffReference = this->GetOutputDiffRef(); + outputDiffReference->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + outputDiffReference->CopyInformation(this->GetInput(0)); + outputDiffReference->Allocate(); + outputDiffReference->FillBuffer(0); + + OutputImagePointerType outputEvolutionTest = this->GetOutputEvolutionTest(); + outputEvolutionTest->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + outputEvolutionTest->CopyInformation(this->GetInput(0)); + outputEvolutionTest->Allocate(); + outputEvolutionTest->FillBuffer(0); + + OutputImagePointerType outputEvolutionReference = this->GetOutputEvolutionRef(); + outputEvolutionReference->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + outputEvolutionReference->CopyInformation(this->GetInput(0)); + outputEvolutionReference->Allocate(); + outputEvolutionReference->FillBuffer(0); + + OutputIteratorType ccReferenceImageIt(this->GetOutputCCReference(), this->GetOutputCCReference()->GetLargestPossibleRegion()); + OutputIteratorType ccTestImageIt(this->GetOutputCCTest(), this->GetOutputCCTest()->GetLargestPossibleRegion()); + + OutputIteratorType outputVoxelIt(outputDiffVoxelWise, outputDiffVoxelWise->GetLargestPossibleRegion()); + + OutputIteratorType outputTestIt(outputDiffTest, outputDiffTest->GetLargestPossibleRegion()); + OutputIteratorType outputRefIt(outputDiffReference, outputDiffReference->GetLargestPossibleRegion()); + + OutputIteratorType outputEvolTestIt(outputEvolutionTest, outputEvolutionTest->GetLargestPossibleRegion()); + OutputIteratorType outputEvolRefIt(outputEvolutionReference, outputEvolutionReference->GetLargestPossibleRegion()); + + while(!ccReferenceImageIt.IsAtEnd()) + { + if((ccReferenceImageIt.Get()!=0) && (ccTestImageIt.Get()!=0)) + { + outputVoxelIt.Set(0); // TP + } + if((ccReferenceImageIt.Get()==0) && (ccTestImageIt.Get()!=0)) + { + outputVoxelIt.Set(1); // FP + } + if((ccReferenceImageIt.Get()!=0) && (ccTestImageIt.Get()==0)) + { + outputVoxelIt.Set(3); // FN + } + + if(m_vect_false_positive_test[ccTestImageIt.Get()]) + { + outputVoxelIt.Set(2); + } + + if(ccReferenceImageIt.Get()!=0) + { + if(m_vect_true_positive_ref[ccReferenceImageIt.Get()]) + { + outputRefIt.Set(1); // TP + if(m_vect_grow_ref[ccReferenceImageIt.Get()]) + { + outputEvolRefIt.Set(4); + } + if(m_vect_shrink_ref[ccReferenceImageIt.Get()]) + { + outputEvolRefIt.Set(3); + } + if(m_vect_same_ref[ccReferenceImageIt.Get()]) + { + outputEvolRefIt.Set(1); + } + } + else + { + outputRefIt.Set(2); // FN + outputEvolRefIt.Set(2); + } + } + + if(ccTestImageIt.Get()!=0) + { + if(m_vect_true_positive_test[ccTestImageIt.Get()]) + { + outputTestIt.Set(1); // TP + if(m_vect_grow_test[ccTestImageIt.Get()]) + { + outputEvolTestIt.Set(4); + } + if(m_vect_shrink_test[ccTestImageIt.Get()]) + { + outputEvolTestIt.Set(3); + } + if(m_vect_same_test[ccTestImageIt.Get()]) + { + outputEvolTestIt.Set(1); + } + } + else + { + outputTestIt.Set(2); // FP + outputEvolTestIt.Set(2); + } + } + + ++ccReferenceImageIt; + ++ccTestImageIt; + ++outputVoxelIt; + ++outputEvolRefIt; + ++outputEvolTestIt; + ++outputTestIt; + ++outputRefIt; + } +} + + + +template +void +ConnectedComponentsMetricsFilter +::Print(std::ostream& fp) +{ + + fp << "** VOLUME INFORMATION: " << std::endl; + fp << "* In Reference Image: " << std::endl; + fp << "-- Original number of connected components: " << m_OriginalNumberOfObjectsRef << std::endl; + fp << "-- Number of connected components after removing small ones: " << m_NumberOfObjectsRef << std::endl; + fp << "-- Total volume: " << m_ReferenceTotalVolume << " mm3" << std::endl; + fp << std::endl; + + fp << "* In Test Image: " << std::endl; + fp << "-- Original number of connected components: " << m_OriginalNumberOfObjectsTest << std::endl; + fp << "-- Number of connected components after removing small ones: " << m_NumberOfObjectsTest << std::endl; + fp << "-- Total volume: " << m_TestTotalVolume << " mm3" << std::endl; + fp << std::endl; + + fp << "-- Volume difference: " << m_VolumeDifference << " mm3" << std::endl; + fp << "-- Volume difference ratio: " << m_VolumeDifferenceRatio << std::endl; + fp << std::endl; + + + fp << "** DETECTION INFORMATION" << std::endl; + + fp << "* In Reference Image: " << std::endl; + fp << "-- Number of true positives: " << m_nb_true_positive_ref << std::endl; + fp << " (connected components number: "; + for(unsigned int k = 1; k < m_vect_true_positive_ref.size(); k++) + { + if(m_vect_true_positive_ref[k]) + fp << k << " "; + } + fp << ")"<< std::endl; + fp << "-- True positive rate: " << m_RefTruePositiveRate << std::endl; + fp << std::endl; + + fp << "-- Number of false negatives (disappeared lesions): " << m_nb_false_negative << std::endl; + fp << " (connected components number: "; + for(unsigned int k = 1; k < m_vect_true_positive_ref.size(); k++) + { + if(!m_vect_true_positive_ref[k]) + fp << k << " "; + } + fp << ")"<< std::endl; + fp << "-- False negatives rate: " << m_FalseNegativeRate << std::endl; + fp << std::endl; + + + fp << "* In Test Image: " << std::endl; + fp << "-- Number of true positives: " << m_nb_true_positive_test << std::endl; + fp << " (connected components number: "; + for(unsigned int k = 1; k < m_vect_true_positive_test.size(); k++) + { + if(m_vect_true_positive_test[k]) + fp << k << " "; + } + fp << ")"<< std::endl; + fp << "-- True positive rate: " << m_TestTruePositiveRate << std::endl; + fp << std::endl; + + fp << "-- Number of false positives (new lesions): " << m_nb_false_positive << std::endl; + fp << " (connected components number: "; + for(unsigned int k = 1; k < m_vect_true_positive_test.size(); k++) + { + if(!m_vect_true_positive_test[k]) + fp << k << " "; + } + fp << ")"<< std::endl; + fp << "-- False positive rate: " << m_FalsePositiveRate << std::endl; + fp << std::endl; + + + fp << "** EVOLUTION INFORMATION" << std::endl; + + fp << "* In Reference Image: " << std::endl; + fp << "-- Number of connected components that grow: " << m_nb_grow_ref << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_grow_ref.size(); j++) + { + if(m_vect_grow_ref[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Growing connected component rate: " << m_RefGrowRate << std::endl; + fp << std::endl; + fp << "-- Number of connected components that shrink: " << m_nb_shrink_ref << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_shrink_ref.size(); j++) + { + if(m_vect_shrink_ref[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Shrinking connected component rate: " << m_RefShrinkRate << std::endl; + fp << std::endl; + fp << "-- Number of connected components that stay the same: " << m_nb_same_ref << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_same_ref.size(); j++) + { + if(m_vect_same_ref[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Same connected component rate: " << m_RefSameRate << std::endl; + fp << std::endl; + + + fp << "* In Test Image: " << std::endl; + fp << "-- Number of connected components that have grown: " << m_nb_grow_test << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_grow_test.size(); j++) + { + if(m_vect_grow_test[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Growing connected component rate: " << m_TestGrowRate << std::endl; + fp << std::endl; + fp << "-- Number of connected components that have shrink: " << m_nb_shrink_test << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_shrink_test.size(); j++) + { + if(m_vect_shrink_test[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Shrinking connected component rate: " << m_TestShrinkRate << std::endl; + fp << std::endl; + fp << "-- Number of connected components that have stayed the same: " << m_nb_same_test << std::endl; + fp << " (connected components number: "; + for(unsigned int j = 1; j < m_vect_same_test.size(); j++) + { + if(m_vect_same_test[j]){fp << j << " ";} + } + fp << ")"<< std::endl; + fp << "-- Same connected component rate: " << m_TestSameRate << std::endl; + fp << std::endl; + fp << std::endl; + fp << std::endl; + +} + +} //end of namespace anima diff --git a/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.h b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.h new file mode 100644 index 000000000..4c0ad2e04 --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace anima +{ + +template +class ConnectedComponentsVolumeFilter : +public itk::ImageToImageFilter< ImageType , ImageType > +{ +public: + /** Standard class typedefs. */ + typedef ConnectedComponentsVolumeFilter Self; + typedef itk::ImageToImageFilter< ImageType, ImageType > 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(ConnectedComponentsVolumeFilter, ImageToImageFilter) + + /** Image typedef support */ + + /** Type of image. */ + typedef typename ImageType::Pointer ImagePointerType; + typedef typename ImageType::ConstPointer ImageConstPointerType; + typedef typename ImageType::PixelType ImagePixelType; + typedef typename ImageType::IndexType ImageIndexType; + typedef typename itk::ImageRegionIterator< ImageType > ImageIteratorType; + typedef typename itk::ImageRegionConstIterator< ImageType > ImageConstIteratorType; + + typedef typename itk::ConnectedComponentImageFilter ConnectedComponentFilterType; + typedef typename itk::RelabelComponentImageFilter RelabelComponentFilterType; + + typedef typename ImageType::SpacingType spacingType; + typedef typename ImageType::SpacingValueType spacingValueType; + + void WriteOutputs(); + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + itkSetMacro(MinSizeMM3, double) + itkGetMacro(MinSizeMM3, double) + + itkSetMacro(FullyConnected, bool) + itkGetMacro(FullyConnected, bool) + + itkSetMacro(Verbose, bool) + itkGetMacro(Verbose, bool) + + itkSetMacro(Dimension, unsigned int) + itkGetMacro(Dimension, unsigned int) + + itkGetMacro(OriginalNumberOfObjects, unsigned int) + itkGetMacro(NumberOfObjects, unsigned int) + itkGetMacro(TotalVolume, double) + + void SetOutputFilename(std::string outputFilename) {m_Outputfilename=outputFilename;} + std::string GetOutputFilename() {return m_Outputfilename;} + + void SetOutputVolumeFilename(std::string outputFilename) {m_OutputVolumefilename=outputFilename;} + std::string GetOutputVolumefFilename() {return m_OutputVolumefilename;} + + std::vector GetVolumeVector() {return m_vect_volume_lesionMM3;} + + spacingType GetSpacing(){return m_Spacing;} + spacingValueType GetSpacingTot(){return m_SpacingTot;} + +protected: + ConnectedComponentsVolumeFilter() + { + this->SetNumberOfRequiredOutputs(1); + this->SetNumberOfRequiredInputs(1); + + m_MinSizeMM3 = 0; + m_MinSizeVoxel = 0; + m_Dimension = 2; + m_FullyConnected = false; + m_Verbose = false; + + m_OriginalNumberOfObjects = 0; + m_NumberOfObjects = 0; + m_TotalVolume= 0; + } + + virtual ~ConnectedComponentsVolumeFilter() + { + } + + void Print(std::ostream& os); + void ComputeSpacing(); + void ComputeMinimumLesionSize(); + void GenerateData() ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(ConnectedComponentsVolumeFilter); + + // Parameters + double m_MinSizeMM3; + unsigned int m_MinSizeVoxel; + unsigned int m_Dimension; + bool m_FullyConnected; + bool m_Verbose; + + // Output + std::string m_Outputfilename; + std::string m_OutputVolumefilename; + unsigned int m_OriginalNumberOfObjects; + unsigned int m_NumberOfObjects; + double m_TotalVolume; + std::vector m_vect_volume_lesionMM3; + spacingType m_Spacing; + spacingValueType m_SpacingTot; +}; + +} // end of namespace anima + +#include "animaConnectedComponentsVolumeFilter.hxx" diff --git a/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.hxx b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.hxx new file mode 100644 index 000000000..5234ceb17 --- /dev/null +++ b/Anima/segmentation/commands/connected_components_metrics/animaConnectedComponentsVolumeFilter.hxx @@ -0,0 +1,141 @@ +#pragma once + +#include "animaConnectedComponentsVolumeFilter.h" + +namespace anima +{ +template +void +ConnectedComponentsVolumeFilter +::WriteOutputs() +{ + if( m_Outputfilename != "" ) + { + std::cout << "Writing output image to: " << m_Outputfilename << std::endl; + anima::writeImage(m_Outputfilename, this->GetOutput()); + } +} + +template +void +ConnectedComponentsVolumeFilter +::ComputeSpacing() +{ + // Compute Image Spacing, 4th dimension is not physical but temporal + m_Spacing = this->GetInput()->GetSpacing(); + m_SpacingTot = m_Spacing[0]; + for (unsigned int i = 1; i < std::min(m_Dimension,(unsigned int)3);++i) + m_SpacingTot *= m_Spacing[i]; +} + +template +void +ConnectedComponentsVolumeFilter +::ComputeMinimumLesionSize() +{ + // Compute minimum lesion size in number of voxels + double epsilon = 10e-6; + double minSizeInVoxelD = m_MinSizeMM3 / m_SpacingTot; + minSizeInVoxelD -= epsilon; + double minSizeInVoxelD_ceil = std::ceil( minSizeInVoxelD ); + m_MinSizeVoxel = static_cast( minSizeInVoxelD_ceil ); +} + +template +void +ConnectedComponentsVolumeFilter +::GenerateData() +{ + this->ComputeSpacing(); + this->ComputeMinimumLesionSize(); + + typename ConnectedComponentFilterType::Pointer connectedComponentFilter = ConnectedComponentFilterType::New(); + connectedComponentFilter->SetInput( this->GetInput() ); + connectedComponentFilter->SetFullyConnected( m_FullyConnected ); + if(this->GetNumberOfWorkUnits() > 0) + connectedComponentFilter->SetNumberOfWorkUnits( this->GetNumberOfWorkUnits() ); + + typename RelabelComponentFilterType::Pointer relabelFilter = RelabelComponentFilterType::New(); + relabelFilter->SetInput( connectedComponentFilter->GetOutput() ); + relabelFilter->SetMinimumObjectSize( m_MinSizeVoxel ); + if(this->GetNumberOfWorkUnits() > 0) + relabelFilter->SetNumberOfWorkUnits( this->GetNumberOfWorkUnits() ); + + relabelFilter->GraftOutput( this->GetOutput() ); + relabelFilter->Update(); + this->GraftOutput( relabelFilter->GetOutput() ); + + m_OriginalNumberOfObjects = relabelFilter->GetOriginalNumberOfObjects(); + m_NumberOfObjects = relabelFilter->GetNumberOfObjects(); + + std::vector voxel_volume_lesion(relabelFilter->GetNumberOfObjects()+1, 0); + m_vect_volume_lesionMM3.resize(relabelFilter->GetNumberOfObjects()+1, 0); + m_TotalVolume = 0; + + ImageIteratorType relabelImageIt(relabelFilter->GetOutput(),relabelFilter->GetOutput()->GetLargestPossibleRegion()); + while(!relabelImageIt.IsAtEnd()) + { + if(relabelImageIt.Get()!=0) + { + voxel_volume_lesion[relabelImageIt.Get()]++; + } + ++relabelImageIt; + } + + for(unsigned int i = 1; i < voxel_volume_lesion.size(); i++) + { + m_vect_volume_lesionMM3[i] = voxel_volume_lesion[i] * m_SpacingTot; + m_TotalVolume += m_vect_volume_lesionMM3[i]; + } + + if(m_OutputVolumefilename.size()!=0) + { + std::ofstream fp(m_OutputVolumefilename.c_str(), std::ios_base::out | std::ios_base::trunc); + if(!fp) + { + std::cerr << "cannot open output file" << m_OutputVolumefilename << std::endl; + } + else + { + this->Print(fp); + fp.close(); + } + } + + if(m_Verbose) + { + this->Print(std::cout); + } + +} + + +template +void +ConnectedComponentsVolumeFilter +::Print(std::ostream& fp) +{ + fp << "Original number of objects: " << m_OriginalNumberOfObjects < + +namespace anima +{ +template +class PickLesionSeedImageFilter : +public itk::ImageToImageFilter +{ +public: + /** Standard class typedefs. */ + typedef PickLesionSeedImageFilter Self; + typedef typename itk::ImageToImageFilter< TInputImage, TOutputImage > Superclass; + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + 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(PickLesionSeedImageFilter, ImageToImageFilter) + + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TInputImage::PixelType InputImagePixel; + typedef typename TInputImage::IndexType IndexType; + typedef typename TOutputImage::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + + itkSetMacro(NumberOfSeeds, unsigned int) + itkSetMacro(ProximityThreshold, double) + + struct pair_comparator + { + bool operator() (const std::pair & f, const std::pair & s) + { return (f.second < s.second); } + }; + +protected: + PickLesionSeedImageFilter() + { + m_NumberOfSeeds = 1; + m_ProximityThreshold = 10; + srand(time(0)); + } + + virtual ~PickLesionSeedImageFilter() {} + + void GenerateData() ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(PickLesionSeedImageFilter); + + unsigned int m_NumberOfSeeds; + double m_ProximityThreshold; +}; + +} // end namespace anima + +#include "animaPickLesionSeedImageFilter.hxx" diff --git a/Anima/segmentation/lesion-simulation/animaPickLesionSeedImageFilter.hxx b/Anima/segmentation/lesion-simulation/animaPickLesionSeedImageFilter.hxx new file mode 100644 index 000000000..342143862 --- /dev/null +++ b/Anima/segmentation/lesion-simulation/animaPickLesionSeedImageFilter.hxx @@ -0,0 +1,96 @@ +#pragma once +#include "animaPickLesionSeedImageFilter.h" + +#include +#include + +#include + +namespace anima +{ + +template +void +PickLesionSeedImageFilter +::GenerateData() +{ + this->AllocateOutputs(); + this->GetOutput()->FillBuffer(0); + + typedef itk::ImageRegionConstIterator InputImageIteratorType; + typedef std::vector < std::pair > VectorType; + typedef typename VectorType::iterator VectorIteratorType; + + std::vector < std::pair > cumulativeDistribution; + + InputImageIteratorType inItr(this->GetInput(),this->GetInput()->GetLargestPossibleRegion()); + while (!inItr.IsAtEnd()) + { + if (inItr.Get() != 0) + cumulativeDistribution.push_back(std::make_pair(inItr.GetIndex(), inItr.Get())); + + ++inItr; + } + + double sumProbas = 0; + unsigned int vecSize = cumulativeDistribution.size(); + + for (unsigned int i = 0;i < vecSize;++i) + sumProbas += cumulativeDistribution[i].second; + + double tmpSum = cumulativeDistribution[0].second / sumProbas; + cumulativeDistribution[0].second /= sumProbas; + for (unsigned int i = 1;i < vecSize;++i) + { + tmpSum += cumulativeDistribution[i].second / sumProbas; + cumulativeDistribution[i].second = tmpSum; + } + + typedef typename TInputImage::PointType PointType; + std::vector selectedPoints; + std::pair tmpData; + PointType tmpPoint, tmpDist; + + for (unsigned int i = 0;i < m_NumberOfSeeds;++i) + { + bool loopSelection = true; + while (loopSelection) + { + double randomValue = (double)(rand()) / RAND_MAX; + + tmpData.second = randomValue; + VectorIteratorType selectedData = std::lower_bound(cumulativeDistribution.begin(),cumulativeDistribution.end(), + tmpData,pair_comparator()); + + if (selectedData == cumulativeDistribution.end()) + continue; + + this->GetInput()->TransformIndexToPhysicalPoint((*selectedData).first,tmpPoint); + + bool tooClose = false; + for (unsigned int j = 0;j < selectedPoints.size();++j) + { + for (unsigned int k = 0;k < tmpPoint.GetPointDimension();++k) + tmpDist[k] = selectedPoints[j][k] - tmpPoint[k]; + + double dist = anima::ComputeNorm(tmpDist); + if (dist <= m_ProximityThreshold) + { + tooClose = true; + break; + } + } + + if (tooClose) + continue; + + selectedPoints.push_back(tmpPoint); + loopSelection = false; + + std::cout << "Setting seed " << i+1 << " at " << (*selectedData).first << std::endl; + this->GetOutput()->SetPixel((*selectedData).first,i+1); + } + } +} + +} // end of namespace anima diff --git a/Anima/segmentation/lesion-simulation/grow_lesion_seeds/CMakeLists.txt b/Anima/segmentation/lesion-simulation/grow_lesion_seeds/CMakeLists.txt new file mode 100644 index 000000000..0c456d6a5 --- /dev/null +++ b/Anima/segmentation/lesion-simulation/grow_lesion_seeds/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaGrowLesionSeeds) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/lesion-simulation/grow_lesion_seeds/animaGrowLesionSeeds.cxx b/Anima/segmentation/lesion-simulation/grow_lesion_seeds/animaGrowLesionSeeds.cxx new file mode 100644 index 000000000..d4687dd16 --- /dev/null +++ b/Anima/segmentation/lesion-simulation/grow_lesion_seeds/animaGrowLesionSeeds.cxx @@ -0,0 +1,67 @@ +#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","Input seed image",true,"","input seed image",cmd); + TCLAP::ValueArg probaArg("p","probafile","Input probability image",true,"","input probability image",cmd); + TCLAP::ValueArg outArg("o","outputfile","Output lesions image",true,"","output lesions image",cmd); + + TCLAP::ValueArg numStepsArg("s","numsteps","Number of steps (default: 100)",false,100,"number of steps",cmd); + TCLAP::ValueArg stepLengthArg("l","steplength","Time step (default: 0.1)",false,0.1,"time step",cmd); + TCLAP::ValueArg diffSourceArg("d","diffsource","Source of diffusion intensity (default: 0)",false,0,"diffusion source intensity",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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image ImageType; + typedef itk::ImageFileReader ImageReaderType; + + ImageReaderType::Pointer inputRead = ImageReaderType::New(); + inputRead->SetFileName(inArg.getValue()); + inputRead->Update(); + + typedef itk::Image ProbaImageType; + typedef itk::ImageFileReader ProbaImageReaderType; + typedef itk::ImageFileWriter ProbaImageWriterType; + + ProbaImageReaderType::Pointer probaRead = ProbaImageReaderType::New(); + probaRead->SetFileName(probaArg.getValue()); + probaRead->Update(); + + typedef anima::InhomogeneousDiffusionImageFilter MainFilterType; + + MainFilterType::Pointer mainFilter = MainFilterType::New(); + + mainFilter->SetInput(inputRead->GetOutput()); + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + mainFilter->SetNumberOfSteps(numStepsArg.getValue()); + mainFilter->SetStepLength(stepLengthArg.getValue()); + mainFilter->SetDiffusionSourceFactor(diffSourceArg.getValue()); + mainFilter->SetDiffusionScalarsImage(probaRead->GetOutput()); + + mainFilter->Update(); + + ProbaImageWriterType::Pointer diffWriter = ProbaImageWriterType::New(); + diffWriter->SetInput(mainFilter->GetOutput()); + diffWriter->SetFileName(outArg.getValue()); + diffWriter->SetUseCompression(true); + + diffWriter->Update(); + + return 0; +} diff --git a/Anima/segmentation/lesion-simulation/pick_lesion_seeds/CMakeLists.txt b/Anima/segmentation/lesion-simulation/pick_lesion_seeds/CMakeLists.txt new file mode 100644 index 000000000..cf8e0cb3c --- /dev/null +++ b/Anima/segmentation/lesion-simulation/pick_lesion_seeds/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaPickLesionSeeds) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/lesion-simulation/pick_lesion_seeds/animaPickLesionSeeds.cxx b/Anima/segmentation/lesion-simulation/pick_lesion_seeds/animaPickLesionSeeds.cxx new file mode 100644 index 000000000..c1c0d098b --- /dev/null +++ b/Anima/segmentation/lesion-simulation/pick_lesion_seeds/animaPickLesionSeeds.cxx @@ -0,0 +1,60 @@ +#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","Input probability image",true,"","input probability image",cmd); + TCLAP::ValueArg outArg("o","outputfile","Output seeds image",true,"","output seeds image",cmd); + + TCLAP::ValueArg minDistArg("d","dist","Minimal distance between seeds (default: 10 mm)",false,10,"minimal distance between seeds",cmd); + TCLAP::ValueArg numSeedsArg("n","numseeds","Number of seeds (default: 1)",false,1,"number of seeds",cmd); + + try + { + cmd.parse(argc,argv); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef anima::PickLesionSeedImageFilter , itk::Image > MainFilterType; + typedef MainFilterType::InputImageType InputImageType; + typedef MainFilterType::OutputImageType OutputImageType; + typedef itk::ImageFileReader itkImageReader; + typedef itk::ImageFileWriter itkImageWriter; + + itkImageReader::Pointer imRead = itkImageReader::New(); + imRead->SetFileName(inArg.getValue()); + imRead->Update(); + + MainFilterType::Pointer mainFilter = MainFilterType::New(); + mainFilter->SetInput(imRead->GetOutput()); + + mainFilter->SetNumberOfSeeds(numSeedsArg.getValue()); + mainFilter->SetProximityThreshold(minDistArg.getValue()); + + try + { + mainFilter->Update(); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return 1; + } + + itkImageWriter::Pointer tmpWriter = itkImageWriter::New(); + tmpWriter->SetFileName(outArg.getValue()); + tmpWriter->SetUseCompression(true); + tmpWriter->SetInput(mainFilter->GetOutput()); + + tmpWriter->Update(); + + return 0; +} diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt new file mode 100644 index 000000000..db89089f2 --- /dev/null +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS) + +project(animaQMRILesionSampleCreator) + +## ############################################################################# +## 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} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx new file mode 100644 index 000000000..b6bd6610d --- /dev/null +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRILesionSampleCreator.cxx @@ -0,0 +1,134 @@ +#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","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); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return(1); + } + + typedef itk::Image ImageType; + typedef itk::ImageFileReader ImageReaderType; + + typedef anima::QMRISampleCreationImageFilter < itk::Image ,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) + { + 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()); + ++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) + { + ImageReaderType::Pointer tmpReader = ImageReaderType::New(); + tmpReader->SetFileName(tmpStr); + tmpReader->Update(); + + 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; + std::string outFileBaseName = outArg.getValue() + "_"; + + 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() != "") + { + typedef itk::ImageFileWriter MaskImageWriterType; + + MaskImageWriterType::Pointer maskWriter = MaskImageWriterType::New(); + maskWriter->SetInput(mainFilter->GetLesionsOutputMask()); + maskWriter->SetFileName(outLesionMaskArg.getValue()); + + maskWriter->Update(); + } + + 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 new file mode 100644 index 000000000..e42233f90 --- /dev/null +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.h @@ -0,0 +1,105 @@ +#pragma once + +#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; + + typedef itk::ImageToImageFilter Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + typedef itk::Image MaskImageType; + typedef MaskImageType::Pointer MaskImagePointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self) + + /** 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; + + itkSetMacro(QMRILesionRelationshipsFile, std::string) + void ReadLesionSizesDistributions(std::string sizeDistributionFile); + + void AddQMRIVarianceImage(TInputImage *varImage); + + void SetLesionsProbabilityMap (TInputImage *probaImage) + { + m_LesionsProbabilityMap = probaImage; + } + + itkSetMacro(LesionDiffusionThreshold, double) + itkSetMacro(LesionMinimalSize, unsigned int) + itkSetMacro(MinimalDistanceBetweenLesions, double) + itkSetMacro(NumberOfSeeds, unsigned int) + + itkGetMacro(LesionsOutputMask, MaskImageType *) + +protected: + QMRISampleCreationImageFilter(); + virtual ~QMRISampleCreationImageFilter() {} + + void GenerateData() ITK_OVERRIDE; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(QMRISampleCreationImageFilter); + + void CheckDataCoherence(); + void InitializeOutputs(); + void ReadQMRILesionRelationships(); + + void GenerateQMRIHealthySamples(); + void GenerateAndGrowLesions(); + void UpdateQMRIOnLesions(); + double GetRandomLesionSizeFromDistribution(); + + std::vector m_QMRIStdevImages; + InputImagePointer m_LesionsProbabilityMap; + + std::vector m_XAxisLesionSizesDistribution; + std::vector m_YAxisLesionSizesDistribution; + + MaskImagePointer m_LesionsOutputMask; + + //! 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; + + 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; + + unsigned int m_NumberOfSeeds; + unsigned int m_NumberOfIndividualLesionsKept; + + //! Random generator + std::mt19937 m_Generator; +}; + +} // end of namespace anima + +#include "animaQMRISampleCreationImageFilter.hxx" diff --git a/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx new file mode 100644 index 000000000..93273471f --- /dev/null +++ b/Anima/segmentation/lesion-simulation/qmri_lesion_sample_creator/animaQMRISampleCreationImageFilter.hxx @@ -0,0 +1,582 @@ +#pragma once +#include "animaQMRISampleCreationImageFilter.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace anima +{ + +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)); + + m_QMRIStdevImages.clear(); + m_XAxisLesionSizesDistribution.clear(); + m_YAxisLesionSizesDistribution.clear(); + + m_LesionDiffusionThreshold = 1; + + m_MinimalDistanceBetweenLesions = 10; + m_LesionMinimalSize = 5; + + m_QMRILesionMeanRelationships.clear(); + + m_NumberOfSeeds = 5; + m_NumberOfIndividualLesionsKept = 0; + + 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()); + + sqrtFilter->Update(); + + typename TInputImage::Pointer tmpImage = sqrtFilter->GetOutput(); + tmpImage->DisconnectPipeline(); + m_QMRIStdevImages.push_back(tmpImage); +} + +template +void QMRISampleCreationImageFilter +::InitializeOutputs() +{ + unsigned int numInputs = this->GetNumberOfIndexedInputs(); + + for (unsigned int i = 0;i < numInputs;++i) + this->SetNthOutput(i, this->MakeOutput(i)); + + this->AllocateOutputs(); +} + +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..."); + + 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); + + 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::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()); + + 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 (pos >= this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Malformed qMRI relationship text file"); + + 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()) + 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"); + + ++pos; + } + + if (pos != this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Malformed qMRI relationship text file"); + + textFile.close(); + + 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); + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + eVals[i] = sqrt(eVals[i]); + + anima::RecomposeTensor(eVals,eVecs,m_QMRILesionCovarianceRelationship); +} + +template +void +QMRISampleCreationImageFilter +::ReadLesionSizesDistributions(std::string sizeDistributionFile) +{ + m_XAxisLesionSizesDistribution.clear(); + m_YAxisLesionSizesDistribution.clear(); + + std::ifstream textFile(sizeDistributionFile.c_str()); + + if (!textFile.is_open()) + itkExceptionMacro("Could not load size distribution text file..."); + + char tmpStr[8192]; + double currentTotal = 0; + while (!textFile.eof()) + { + textFile.getline(tmpStr,8192); + if (strcmp(tmpStr,"") == 0) + continue; + + double a0, a1; + + std::stringstream tmpStrStream(tmpStr); + tmpStrStream >> a0 >> a1; + + m_XAxisLesionSizesDistribution.push_back(a0); + m_YAxisLesionSizesDistribution.push_back(currentTotal + a1); + currentTotal += a1; + } + + if (currentTotal != 1) + { + for (unsigned int i = 0;i < m_YAxisLesionSizesDistribution.size();++i) + m_YAxisLesionSizesDistribution[i] /= currentTotal; + } + + textFile.close(); +} + +template +void +QMRISampleCreationImageFilter +::CheckDataCoherence() +{ + if (m_QMRIStdevImages.size() != this->GetNumberOfIndexedInputs()) + itkExceptionMacro("Not the same number of inputs and qMRI variance images..."); + + if (m_LesionsProbabilityMap.IsNull()) + itkExceptionMacro("No lesion probability mask input..."); + + if (this->GetNumberOfIndexedInputs() != m_QMRILesionMeanRelationships.size()) + itkExceptionMacro("Relationships for lesion generation should have the same size as the number of inputs..."); + + if (m_XAxisLesionSizesDistribution.size() == 0) + itkExceptionMacro("A distribution of lesion sizes must be given..."); +} + +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; + + 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()); + + multiplyFilter->Update(); + + typename AddFilterType::Pointer addFilter = AddFilterType::New(); + addFilter->SetInput1(this->GetInput(i)); + addFilter->SetInput2(multiplyFilter->GetOutput()); + addFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + addFilter->Update(); + + typename TOutputImage::Pointer tmpOut = addFilter->GetOutput(); + tmpOut->DisconnectPipeline(); + + this->SetNthOutput(i,tmpOut); + } +} + +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); + + seedLesionsGenerator->Update(); + + typename MaskImageType::Pointer outputSeedImage = seedLesionsGenerator->GetOutput(); + + 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->Allocate(); + + 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; + + 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()); + + maskThrFilter->Update(); + + typedef itk::ConnectedComponentImageFilter CCFilterType; + CCFilterType::Pointer ccFilter = CCFilterType::New(); + ccFilter->SetInput(maskThrFilter->GetOutput()); + ccFilter->SetFullyConnected(false); + + ccFilter->Update(); + + 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) + { + ++ccItr; + continue; + } + + if (ccVal > componentSizes.size()) + { + while (ccVal > componentSizes.size()) + componentSizes.push_back(0); + } + + componentSizes[ccVal-1]++; + ++ccItr; + } + + unsigned int pos = 1; + for (unsigned int i = 0;i < componentSizes.size();++i) + { + if (componentSizes[i] > m_LesionMinimalSize) + { + componentSizes[i] = pos; + ++pos; + } + else + componentSizes[i] = 0; + } + + 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; +} + +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) + { + 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(); + + distFilter->SetInput(m_LesionsOutputMask); + distFilter->InsideIsPositiveOn(); + distFilter->UseImageSpacingOn(); + distFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + 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) + { + ++lesionsItr; + ++distMapItr; + continue; + } + + unsigned int lesionNumber = lesionsItr.Get()-1; + if (maxDists[lesionNumber] < distMapItr.Get()) + maxDists[lesionNumber] = distMapItr.Get(); + + ++numPixels[lesionNumber]; + + ++lesionsItr; + ++distMapItr; + } + + //Compute multiplying factors for QMRI lesions + std::vector multiplyingFactors(this->GetNumberOfIndexedInputs(),0); + bool validFactors = false; + while (!validFactors) + { + anima::SampleFromMultivariateGaussianDistribution(m_QMRILesionMeanRelationships,m_QMRILesionCovarianceRelationship, + multiplyingFactors,m_Generator,false); + + validFactors = true; + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + { + if (multiplyingFactors[i] <= 1) + { + validFactors = false; + break; + } + } + } + + // 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(); + + std::vector meanValues(m_NumberOfIndividualLesionsKept,0); + while (!lesionsItr.IsAtEnd()) + { + if (lesionsItr.Get() == 0) + { + ++lesionsItr; + ++outItr; + continue; + } + + unsigned int lesionNumber = lesionsItr.Get()-1; + meanValues[lesionNumber] += outItr.Get(); + + ++outItr; + ++lesionsItr; + } + + for (unsigned int j = 0;j < m_NumberOfIndividualLesionsKept;++j) + { + if (maxDists[j] == 0) + maxDists[j] = 1; + + meanValues[j] /= numPixels[j]; + } + + distMapItr.GoToBegin(); + lesionsItr.GoToBegin(); + outItr.GoToBegin(); + + 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 diff --git a/Anima/segmentation/staple/CMakeLists.txt b/Anima/segmentation/staple/CMakeLists.txt new file mode 100644 index 000000000..c0caa3ebd --- /dev/null +++ b/Anima/segmentation/staple/CMakeLists.txt @@ -0,0 +1,37 @@ +if(BUILD_TOOLS) + +project(animaMultiThreadedSTAPLE) + +## ############################################################################# +## 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} + ITKMathematicalMorphology + ${ITKIO_LIBRARIES} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/segmentation/staple/animaMultiThreadedSTAPLE.cxx b/Anima/segmentation/staple/animaMultiThreadedSTAPLE.cxx new file mode 100644 index 000000000..5861a2562 --- /dev/null +++ b/Anima/segmentation/staple/animaMultiThreadedSTAPLE.cxx @@ -0,0 +1,153 @@ +#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 lstArg("l","listname","File containing a list of segmentations",true,"","image list",cmd); + TCLAP::ValueArg resArg("o","outputname","Result image",true,"","result image",cmd); + TCLAP::ValueArg gtPriorArg("g","gtprior","Vector image of ground truth prior probabilities (for advanced use)",false,"","GT prior probabilities",cmd); + TCLAP::ValueArg presArg("s","outputparameters","Outputs expert parameters as an image",false,"","output parameters image",cmd); + TCLAP::ValueArg lmapArg("O","outputlabelmap","Outputs the classification as a label map",false,"","output label map",cmd); + + TCLAP::ValueArg maskArg("m","maskname","Computation mask",false,"","computation mask",cmd); + TCLAP::ValueArg maskDilRadiusArg("","mask-dil","Dilation radius to compute default work mask as dilated union of inputs, 0 means use the full image (default: 2)",false,2,"mask dilation radius",cmd); + + TCLAP::ValueArg iniArg("d","initialparametervalue","Initial value of the diagonal parameters in STAPLE (default : 0.99)",false,0.99,"initial diagonal parameters",cmd); + TCLAP::ValueArg thrArg("t","relativethreshold","Relative stopping criterion for Staple (default : 1.0e-8)",false,1.0e-8,"stopping criterion",cmd); + + TCLAP::SwitchArg dmapArg("D","diagonalmap","Activate diagonal MAP estimator",cmd,false); + TCLAP::SwitchArg fmapArg("F","fullmap","Activate full MAP estimator (overrides diagonal MAP)",cmd,false); + + TCLAP::SwitchArg mStrArg("M","missingstr","Account for missing structures. Used only if together with full MAP estimator",cmd,false); + + TCLAP::ValueArg weightArg("w","mapweighting","Parameter to increase the weight of the prior in MAP estimation (default : 0.5)",false,0.5,"MAP weighting parameter",cmd); + + TCLAP::ValueArg ampArg("a","alphadiagonalmap","Alpha parameter for the diagonal beta distributions (default : 5.0)",false,5.0,"diagonal alpha parameter",cmd); + TCLAP::ValueArg bmpArg("b","betadiagonalmap","Beta parameter for the diagonal beta distributions (default : 1.5)",false,1.5,"diagonal beta parameter",cmd); + + TCLAP::ValueArg ampoArg("A","alphaoffdiagonalmap","Alpha parameter for the off-diagonal beta distributions (default : 1.5)",false,1.5,"off-diagonal alpha parameter",cmd); + TCLAP::ValueArg bmpoArg("B","betaoffdiagonalmap","Beta parameter for the off-diagonal beta distributions (default : 5.0)",false,5.0,"off-diagonal beta parameter",cmd); + + TCLAP::ValueArg iterArg("i","iterations","Maximum number of iterations (default: 100)",false,100,"number of iterations",cmd); + TCLAP::ValueArg nbpArg("T","numberofthreads","Number of threads to run on (default : all available 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(1); + } + + int itMax = iterArg.getValue(); + double iniDiag = iniArg.getValue(); + double relThr = thrArg.getValue(); + + unsigned int nbProcs = nbpArg.getValue(); + + typedef itk::Image InputImageType; + typedef itk::Image ParamsImageType; + typedef anima::MultiThreadedSTAPLEImageFilter StapleFilterType; + typedef itk::VectorImage OutputImageType; + itk::TimeProbe tmpTime; + tmpTime.Start(); + + StapleFilterType::Pointer stapleFilter = StapleFilterType::New(); + stapleFilter->SetVerbose(true); + stapleFilter->SetMAPWeighting(weightArg.getValue()); + stapleFilter->SetAccountForMissingStructures(mStrArg.getValue()); + stapleFilter->SetMaximumIterations(itMax); + stapleFilter->SetRelativeConvergenceThreshold(relThr); + + if (maskArg.getValue() != "") + stapleFilter->SetComputationMask(anima::readImage < itk::Image >(maskArg.getValue())); + + stapleFilter->SetMaskDilationRadius(maskDilRadiusArg.getValue()); + + if (gtPriorArg.getValue() != "") + stapleFilter->SetGTPriorImage(anima::readImage < itk::VectorImage >(gtPriorArg.getValue())); + + std::ifstream fileIn(lstArg.getValue().c_str()); + if (!fileIn.is_open()) + { + std::cerr << "Input file list " << lstArg.getValue() << " does not exist... Exiting..." << std::endl; + return EXIT_FAILURE; + } + + int nbPats = 0; + + while (!fileIn.eof()) + { + char tmpStr[2048]; + fileIn.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + std::cout << "Loading image " << nbPats << " " << tmpStr << "..." << std::endl; + stapleFilter->SetInput(nbPats,anima::readImage (tmpStr)); + + nbPats++; + } + + fileIn.close(); + + stapleFilter->InitializeNbClassesFromData(); + stapleFilter->InitializeExpertParameters(iniDiag); + stapleFilter->InitializePriorFromData(); + + if ((dmapArg.isSet()) || (fmapArg.isSet())) + { + double alphaMAP = ampArg.getValue(); + double betaMAP = bmpArg.getValue(); + double alphaMAPNonDiag = ampoArg.getValue(); + double betaMAPNonDiag = bmpoArg.getValue(); + + if (dmapArg.isSet()) + { + stapleFilter->SetMAPUpdate(StapleFilterType::DIAGONAL_MAP); + stapleFilter->SetAlphaMAP(alphaMAP); + stapleFilter->SetBetaMAP(betaMAP); + } + else + { + stapleFilter->SetMAPUpdate(StapleFilterType::FULL_MAP); + stapleFilter->SetAlphaMAP(alphaMAP); + stapleFilter->SetBetaMAP(betaMAP); + stapleFilter->SetAlphaMAPNonDiag(alphaMAPNonDiag); + stapleFilter->SetBetaMAPNonDiag(betaMAPNonDiag); + } + } + else + stapleFilter->SetMAPUpdate(StapleFilterType::STANDARD); + + stapleFilter->SetNumberOfWorkUnits(nbProcs); + stapleFilter->Update(); + + if (resArg.getValue() != "") + anima::writeImage (resArg.getValue(),stapleFilter->GetOutput()); + + if (lmapArg.getValue() != "") + anima::writeImage (lmapArg.getValue(),stapleFilter->GetClassificationAsLabelMap()); + + if (presArg.getValue() == "") + stapleFilter->PrintPerformanceParameters(); + else + anima::writeImage (presArg.getValue(),stapleFilter->GetExpertParametersAsImage()); + + tmpTime.Stop(); + + std::cout << "Total computation time: " << tmpTime.GetTotal() << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.h b/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.h new file mode 100644 index 000000000..f9153d8b2 --- /dev/null +++ b/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.h @@ -0,0 +1,248 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace anima +{ + +template +class MultiThreadedSTAPLEImageFilter : + public anima::MaskedImageToImageFilter< TInputImage, itk::VectorImage > +{ +public: + typedef enum { + STANDARD, + DIAGONAL_MAP, + FULL_MAP + } MAP_TYPE; + + /** Standard class typedefs. */ + typedef MultiThreadedSTAPLEImageFilter Self; + typedef itk::VectorImage TOutputImage; + typedef anima::MaskedImageToImageFilter 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(MultiThreadedSTAPLEImageFilter, anima::MaskedImageToImageFilter) + + 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 InputImageType::IndexType InputImageIndexType; + typedef typename OutputImageType::Pointer OutputImagePointer; + + /** Superclass typedefs. */ + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + typedef typename Superclass::MaskImageType MaskImageType; + typedef typename Superclass::MaskImagePointer MaskImagePointer; + + /** My typedefs */ + typedef vnl_matrix ParametersMatrixType; + + typedef itk::VectorImage GTPriorImageType; + typedef GTPriorImageType::Pointer GTPriorImagePointer; + + /** Set/Get the mask on which to compute STAPLE estimate. */ + itkSetMacro(GTPriorImage, GTPriorImagePointer) + itkGetMacro(GTPriorImage, GTPriorImagePointer) + + /** Get the number of elapsed iterations of the iterative E-M algorithm. */ + itkGetMacro(ElapsedIterations, unsigned int) + + itkSetMacro(Verbose, bool) + + itkGetMacro(AccountForMissingStructures, bool) + itkSetMacro(AccountForMissingStructures, bool) + + /** Set/Get the maximum number of iterations after which the STAPLE algorithm + * will be considered to have converged. */ + itkSetMacro(MaximumIterations, unsigned int) + itkGetMacro(MaximumIterations, unsigned int) + + /** Set/Get the threshold for which a change in the maximum of the difference between parameters + * shall be so small as to trigger termination of the estimation procedure. + */ + itkSetMacro(RelativeConvergenceThreshold, double) + itkGetMacro(RelativeConvergenceThreshold, double) + + /** Compute the M-step of the algorithm + * (i.e. the parameters of each expert from the reference standard and the data). + */ + void EstimatePerformanceParameters(); + void EstimatePerformanceParameters(unsigned int minExp, unsigned int maxExp); + void EstimateMAPPerformanceParameters(unsigned int minExp, unsigned int maxExp); + void EstimateFullMAPPerformanceParameters(unsigned int minExp, unsigned int maxExp); + + bool endConditionReached(); + + void InitializeExpertParameters(double diagValue); + void InitializePriorFromData(); + void InitializeNbClassesFromData(); + + void PrintPerformanceParameters() + { + itk::Indent tmp; + this->PrintSelf(std::cout,tmp); + } + + InputImageType *GetClassificationAsLabelMap(); + + double GetPrior(unsigned int i) + { + if (i > this->GetNumberOfInputs()) + itkExceptionMacro(<< "Array reference out of bounds."); + + return m_Prior[i]; + } + + ParametersMatrixType GetExpertParameters(unsigned int i) const + { + if (i > this->GetNumberOfInputs()) + itkExceptionMacro(<< "Array reference out of bounds."); + + return m_ExpParams[i]; + } + + std::vector GetMAPStructures(unsigned int i) const + { + if (i > this->GetNumberOfInputs()) + itkExceptionMacro(<< "Array reference out of bounds."); + + return m_MAPStructures[i]; + } + + typedef itk::Image ParamsImageType; + ParamsImageType *GetExpertParametersAsImage(); + + /** Set / Get methods for MAP STAPLE */ + itkSetMacro(MAPUpdate, MAP_TYPE) + itkGetMacro(MAPUpdate, MAP_TYPE) + + itkSetMacro(AlphaMAP, double) + itkGetMacro(AlphaMAP, double) + + itkSetMacro(BetaMAP, double) + itkGetMacro(BetaMAP, double) + + itkSetMacro(nbClasses, unsigned int) + itkGetMacro(nbClasses, unsigned int) + + itkSetMacro(AlphaMAPNonDiag, double) + itkGetMacro(AlphaMAPNonDiag, double) + + itkSetMacro(BetaMAPNonDiag, double) + itkGetMacro(BetaMAPNonDiag, double) + + itkSetMacro(MAPWeighting, double) + itkGetMacro(MAPWeighting, double) + + itkSetMacro(EpsilonFixedPoint, double) + itkGetMacro(EpsilonFixedPoint, double) + + itkSetMacro(MaxIterFixedPoint, unsigned int) + itkGetMacro(MaxIterFixedPoint, unsigned int) + + itkSetMacro(MaskDilationRadius, unsigned int) + itkGetMacro(MaskDilationRadius, unsigned int) + +protected: + MultiThreadedSTAPLEImageFilter() + : Superclass() + { + m_OldExpParams.clear(); + m_ExpParams.clear(); + m_Prior.clear(); + m_GTPriorImage = NULL; + m_LabelMap = NULL; + + m_AccountForMissingStructures = false; + m_MAPStructures.clear(); + + m_MaskDilationRadius = 2; + m_MAPWeighting = 1; + m_MAPUpdate = STANDARD; + m_AlphaMAP = 5; + m_BetaMAP = 1.5; + m_AlphaMAPNonDiag = m_BetaMAP; + m_BetaMAPNonDiag = m_AlphaMAP; + + m_MaxIterFixedPoint = 50; + m_EpsilonFixedPoint = 1.0e-6; + + m_Verbose = true; + m_RelativeConvergenceThreshold = 0; + m_MaximumIterations = itk::NumericTraits::max(); + m_ElapsedIterations = 0; + m_nbClasses = 0; + } + + virtual ~MultiThreadedSTAPLEImageFilter() {} + + void CheckComputationMask() ITK_OVERRIDE; + + /** Compute the E-step of the algorithm + * (i.e. the reference standard from the parameters and the data). + */ + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + void InitializeMissingStructures(); + + void GenerateOutputInformation() ITK_OVERRIDE; + //Redefine virtual functions + void GenerateData() ITK_OVERRIDE; + + void ComputeFixedPointMAPEstimates(unsigned int numExp, unsigned int numClass, std::vector &constantNums, long double &constantDenom); + + void PrintSelf(std::ostream&, itk::Indent) const ITK_OVERRIDE; + + struct EMStepThreadStruct + { + Pointer Filter; + }; + + // Does the splitting and calls EstimatePerformanceParameters on a sub sample of experts + static ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION ThreadEstimatePerfParams( void *arg ); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(MultiThreadedSTAPLEImageFilter); + + unsigned int m_ElapsedIterations; + unsigned int m_MaximumIterations; + unsigned int m_MaskDilationRadius; + double m_RelativeConvergenceThreshold; + bool m_Verbose; + bool m_AccountForMissingStructures; + + unsigned int m_nbClasses; + MAP_TYPE m_MAPUpdate; + double m_AlphaMAP, m_BetaMAP, m_AlphaMAPNonDiag, m_BetaMAPNonDiag; + double m_MAPWeighting; + unsigned int m_MaxIterFixedPoint; + double m_EpsilonFixedPoint; + + std::vector < std::vector > m_MAPStructures; + std::vector m_ExpParams, m_OldExpParams; + std::vector m_Prior; + + // Ground truth prior image if someone wants to use something else than uniform class prior in m_Prior + GTPriorImagePointer m_GTPriorImage; + InputImagePointer m_LabelMap; +}; + +} // end namespace anima + +#include "animaMultiThreadedSTAPLEImageFilter.hxx" diff --git a/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.hxx b/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.hxx new file mode 100644 index 000000000..20dc173ba --- /dev/null +++ b/Anima/segmentation/staple/animaMultiThreadedSTAPLEImageFilter.hxx @@ -0,0 +1,947 @@ +#pragma once +#include "animaMultiThreadedSTAPLEImageFilter.h" + +#include +#include +#include +#include +#include +#include + +namespace anima +{ + +template +void +MultiThreadedSTAPLEImageFilter +::CheckComputationMask() +{ + if (this->GetComputationMask()) + return; + + MaskImagePointer tmpMask = MaskImageType::New(); + tmpMask->Initialize(); + + tmpMask->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + tmpMask->SetSpacing(this->GetInput(0)->GetSpacing()); + tmpMask->SetOrigin(this->GetInput(0)->GetOrigin()); + tmpMask->SetDirection(this->GetInput(0)->GetDirection()); + + tmpMask->Allocate(); + tmpMask->FillBuffer(1); + this->SetComputationMask(tmpMask); + + if (m_MaskDilationRadius == 0) + return; + + this->GetComputationMask()->FillBuffer(0); + + typedef itk::ImageRegionConstIterator InIteratorType; + typedef itk::ImageRegionIterator MaskRegionIteratorType; + + MaskRegionIteratorType maskItr(this->GetComputationMask(),this->GetInput(0)->GetLargestPossibleRegion()); + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + { + InIteratorType itr(this->GetInput(i),this->GetInput(i)->GetLargestPossibleRegion()); + + maskItr.GoToBegin(); + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + ++itr; + ++maskItr; + continue; + } + + if (itr.Get() != 0) + maskItr.Set(1); + + ++itr; + ++maskItr; + } + } + + typedef itk::BinaryBallStructuringElement BallElementType; + typedef itk::GrayscaleDilateImageFilter DilateFilterType; + + typename DilateFilterType::Pointer dilateFilter = DilateFilterType::New(); + dilateFilter->SetInput(this->GetComputationMask()); + dilateFilter->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + BallElementType tmpBall; + BallElementType::SizeType ballSize; + ballSize[0] = m_MaskDilationRadius; + ballSize[1] = m_MaskDilationRadius; + ballSize[2] = m_MaskDilationRadius; + tmpBall.SetRadius(ballSize); + tmpBall.CreateStructuringElement(); + + dilateFilter->SetKernel(tmpBall); + dilateFilter->Update(); + + typename MaskImageType::Pointer dilMask = dilateFilter->GetOutput(); + dilMask->DisconnectPipeline(); + + this->SetComputationMask(dilMask); +} + +template +void +MultiThreadedSTAPLEImageFilter +::InitializeMissingStructures() +{ + if (m_nbClasses <= 0) + this->InitializeNbClassesFromData(); + + m_MAPStructures.clear(); + std::vector tmpVec(m_nbClasses,1); + if (!m_AccountForMissingStructures) + { + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + m_MAPStructures.push_back(tmpVec); + + return; + } + + typedef itk::ImageRegionConstIterator InIteratorType; + typedef itk::ImageRegionIterator MaskRegionIteratorType; + + InputImageRegionType largestRegion = this->GetInput(0)->GetLargestPossibleRegion(); + MaskRegionIteratorType maskItr(this->GetComputationMask(),largestRegion); + + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + tmpVec[j] = 0; + + InIteratorType tmpIt(this->GetInput(i), largestRegion); + maskItr.GoToBegin(); + + unsigned int sumStrs = 0; + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + unsigned int tmpVal = tmpIt.Get(); + if (tmpVec[tmpVal] == 0) + { + sumStrs++; + tmpVec[tmpVal] = 1; + } + } + + if (sumStrs == m_nbClasses) + break; + + ++maskItr; + ++tmpIt; + } + + m_MAPStructures.push_back(tmpVec); + } +} + +template +void +MultiThreadedSTAPLEImageFilter +::InitializeExpertParameters(double diagValue) +{ + this->CheckComputationMask(); + + if (m_nbClasses <= 0) + this->InitializeNbClassesFromData(); + + if (m_MAPStructures.size() == 0) + this->InitializeMissingStructures(); + + // m_nbClasses has to have been computed from the images + unsigned int nbExperts = this->GetNumberOfIndexedInputs(); + + double nonDiagValue = (1.0 - diagValue)/(m_nbClasses - 1); + + m_OldExpParams.clear(); + m_ExpParams.clear(); + + ParametersMatrixType tmpMat(m_nbClasses,m_nbClasses); + for (unsigned int i = 0;i < nbExperts;++i) + { + for (unsigned int k = 0;k < m_nbClasses;++k) + { + if (m_MAPStructures[i][k] != 0) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + { + if (j != k) + tmpMat(j,k) = nonDiagValue; + else + tmpMat(j,j) = diagValue; + } + } + else + { + // Missing structure + tmpMat(0,k) = diagValue; + for (unsigned int j = 1;j < m_nbClasses;++j) + tmpMat(j,k) = nonDiagValue; + } + } + + m_OldExpParams.push_back(tmpMat); + m_ExpParams.push_back(tmpMat); + } +} + +template +bool +MultiThreadedSTAPLEImageFilter +::endConditionReached() +{ + double absDiff = 0; + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + { + if (fabs(m_OldExpParams[i](j,k) - m_ExpParams[i](j,k)) > absDiff) + absDiff = fabs(m_OldExpParams[i](j,k) - m_ExpParams[i](j,k)); + } + } + + if (absDiff > m_RelativeConvergenceThreshold) + return false; + else + return true; +} + +template +void +MultiThreadedSTAPLEImageFilter +::InitializeNbClassesFromData() +{ + // In the future implement here a way to rescale non contiguous labels so that they are after + // Right now just get the max value over all images and set it as nbClasses + this->CheckComputationMask(); + + m_nbClasses = 0; + + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + InputImageRegionType largestRegion = this->GetInput(0)->GetLargestPossibleRegion(); + MaskRegionIteratorType maskItr(this->GetComputationMask(),largestRegion); + + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + maskItr.GoToBegin(); + InIteratorType inItr(this->GetInput(i),largestRegion); + inItr.GoToBegin(); + + while(!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + unsigned int tmpVal = inItr.Get(); + m_nbClasses = std::max(m_nbClasses,tmpVal); + } + + ++maskItr; + ++inItr; + } + } + + if (m_nbClasses != 0) + m_nbClasses++; +} + +template +void +MultiThreadedSTAPLEImageFilter +::InitializePriorFromData() +{ + this->CheckComputationMask(); + + if (m_nbClasses <= 0) + this->InitializeNbClassesFromData(); + + if (m_MAPStructures.size() != this->GetNumberOfInputs()) + this->InitializeMissingStructures(); + + if (!m_GTPriorImage.IsNull()) + { + if (m_GTPriorImage->GetNumberOfComponentsPerPixel() == m_nbClasses) + return; + else + itkExceptionMacro("Number of classes in ground truth prior image is not the same as in the images..."); + } + + m_Prior.resize(m_nbClasses); + + for (unsigned int i = 0;i < m_nbClasses;++i) + m_Prior[i] = 0; + + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + InputImageRegionType largestRegion = this->GetInput(0)->GetLargestPossibleRegion(); + + MaskRegionIteratorType maskItr(this->GetComputationMask(),largestRegion); + double nbRealPts = 0; + + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + maskItr.GoToBegin(); + InIteratorType inputIterator(this->GetInput(i), largestRegion); + inputIterator.GoToBegin(); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + m_Prior[inputIterator.Get()]++; + if (i == 0) + nbRealPts++; + } + + ++maskItr; + ++inputIterator; + } + } + + double sumPriors = 0; + + for (unsigned int i = 1;i < m_nbClasses;++i) + { + double nbExpClass = 0; + for (unsigned int j = 0;j < this->GetNumberOfInputs();++j) + nbExpClass += (m_MAPStructures[j][i] != 0); + m_Prior[i] /= (nbRealPts*nbExpClass); + sumPriors += m_Prior[i]; + } + + m_Prior[0] = 1 - sumPriors; +} + +template +void +MultiThreadedSTAPLEImageFilter +::GenerateOutputInformation() +{ + // Override the method in itkImageSource, so we can set the vector length of + // the output itk::VectorImage + + this->Superclass::GenerateOutputInformation(); + + if (m_nbClasses <= 0) + this->InitializeNbClassesFromData(); + + OutputImageType *output = this->GetOutput(); + output->SetVectorLength(m_nbClasses); +} + +template +void +MultiThreadedSTAPLEImageFilter +::GenerateData() +{ + this->AllocateOutputs(); + this->BeforeThreadedGenerateData(); + + if (m_MAPUpdate == DIAGONAL_MAP) + this->SetAccountForMissingStructures(false); + + this->InitializeMissingStructures(); + + if (m_nbClasses != m_Prior.size()) + this->InitializePriorFromData(); + + if ((m_nbClasses == 2)&&(m_MAPUpdate == FULL_MAP)) + { + m_AlphaMAP += m_BetaMAPNonDiag - 1; + m_BetaMAP += m_AlphaMAPNonDiag - 1; + m_MAPUpdate = DIAGONAL_MAP; + } + + if ((m_MAPUpdate == FULL_MAP)&&(m_AlphaMAPNonDiag == 1)&&(m_BetaMAPNonDiag == 1)) + m_MAPUpdate = DIAGONAL_MAP; + + if (this->GetNumberOfInputs() != m_ExpParams.size()) + this->InitializeExpertParameters(0.99); + + unsigned int itncount = 0; + bool continueLoop = true; + while ((itncount < m_MaximumIterations)&&(continueLoop)) + { + if (m_Verbose) + std::cout << "Iteration " << itncount + 1 << "..." << std::endl; + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + // Parallelize by calling DynamicThreadedGenerateData, estimation of reference standard + this->GetMultiThreader()->template ParallelizeImageRegion ( + this->GetOutput()->GetRequestedRegion(), + [this](const OutputImageRegionType & outputRegionForThread) + { this->DynamicThreadedGenerateData(outputRegionForThread); }, this); + + tmpTime.Stop(); + + if (m_Verbose) + std::cout << "Reference standard estimated in " << tmpTime.GetTotal() << "..." << std::endl; + + itk::TimeProbe tmpTimePerf; + tmpTimePerf.Start(); + + EstimatePerformanceParameters(); + + tmpTimePerf.Stop(); + + if (m_Verbose) + std::cout << "Performance parameters estimated in " << tmpTimePerf.GetTotal() << "..." << std::endl; + + ++itncount; + + if (itncount != 1) + continueLoop = !endConditionReached(); + + if (continueLoop) + { + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + m_OldExpParams[i](j,k) = m_ExpParams[i](j,k); + } + } + } + + m_ElapsedIterations = itncount; +} + +template +void +MultiThreadedSTAPLEImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + unsigned int nbExperts = this->GetNumberOfInputs(); + + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex OutRegionIteratorType; + typedef itk::ImageRegionConstIteratorWithIndex GTPriorIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + OutRegionIteratorType outItr(this->GetOutput(),outputRegionForThread); + + GTPriorIteratorType gtPriorIt; + GTPriorImageType::PixelType lPrior(m_nbClasses); + + if (m_GTPriorImage.IsNull()) + { + for (unsigned int i = 0;i < m_nbClasses;++i) + lPrior[i] = m_Prior[i]; + } + else + { + gtPriorIt = GTPriorIteratorType(m_GTPriorImage,outputRegionForThread); + } + + std::vector inputIterators; + for (unsigned int i = 0; i < nbExperts;++i) + { + inputIterators.push_back(InIteratorType(this->GetInput(i), outputRegionForThread)); + inputIterators[i].GoToBegin(); + } + + MaskRegionIteratorType maskItr(this->GetComputationMask(),outputRegionForThread); + + outItr.GoToBegin(); + maskItr.GoToBegin(); + + OutputPixelType tmpClassif(m_nbClasses); + + while (!outItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + ++outItr; + for (unsigned int i = 0; i < nbExperts;++i) + ++inputIterators[i]; + ++maskItr; + + if (!m_GTPriorImage.IsNull()) + ++gtPriorIt; + + continue; + } + + if (!m_GTPriorImage.IsNull()) + lPrior = gtPriorIt.Get(); + + long double denom = 0; + for (unsigned int m = 0;m < m_nbClasses;++m) + { + long double temp = 1; + for (unsigned int k = 0;k < nbExperts;++k) + { + unsigned int l = inputIterators[k].Get(); + temp *= m_ExpParams[k](l,m); + } + denom += lPrior[m]*temp; + } + + for (unsigned int m = 0;m < m_nbClasses;++m) + { + long double pDijTi = 1; + for (unsigned int k = 0;k < nbExperts;++k) + { + unsigned int l = inputIterators[k].Get(); + pDijTi *= m_ExpParams[k](l,m); + } + pDijTi *= lPrior[m]; + + tmpClassif[m] = pDijTi/denom; + } + + outItr.Set(tmpClassif); + + ++outItr; + ++maskItr; + for (unsigned int i = 0; i < nbExperts;++i) + ++inputIterators[i]; + + if (!m_GTPriorImage.IsNull()) + ++gtPriorIt; + } +} + +template +void +MultiThreadedSTAPLEImageFilter +::EstimatePerformanceParameters() +{ + itk::PoolMultiThreader::Pointer threaderMstep = itk::PoolMultiThreader::New(); + + EMStepThreadStruct *tmpStr = new EMStepThreadStruct; + tmpStr->Filter = this; + + unsigned int actualNumberOfThreads = std::min(this->GetNumberOfWorkUnits(),(unsigned int)this->GetNumberOfInputs()); + + threaderMstep->SetNumberOfWorkUnits(actualNumberOfThreads); + threaderMstep->SetSingleMethod(this->ThreadEstimatePerfParams,tmpStr); + threaderMstep->SingleMethodExecute(); + + delete tmpStr; +} + +template +ITK_THREAD_RETURN_FUNCTION_CALL_CONVENTION +MultiThreadedSTAPLEImageFilter +::ThreadEstimatePerfParams(void *arg) +{ + itk::MultiThreaderBase::WorkUnitInfo *threadArgs = (itk::MultiThreaderBase::WorkUnitInfo *)arg; + + unsigned int nbThread = threadArgs->WorkUnitID; + unsigned int nbProcs = threadArgs->NumberOfWorkUnits; + + EMStepThreadStruct *tmpStr = (EMStepThreadStruct *)threadArgs->UserData; + unsigned int nbExperts = tmpStr->Filter->GetNumberOfInputs(); + + unsigned int minExp = (unsigned int)floor((double)nbThread*nbExperts/nbProcs); + unsigned int maxExp = (unsigned int)floor((double)(nbThread + 1.0)*nbExperts/nbProcs); + + maxExp = std::min(nbExperts,maxExp); + + switch (tmpStr->Filter->GetMAPUpdate()) + { + case FULL_MAP: + tmpStr->Filter->EstimateFullMAPPerformanceParameters(minExp,maxExp); + break; + + case DIAGONAL_MAP: + tmpStr->Filter->EstimateMAPPerformanceParameters(minExp,maxExp); + break; + + case STANDARD: + default: + tmpStr->Filter->EstimatePerformanceParameters(minExp,maxExp); + break; + } + + return ITK_THREAD_RETURN_DEFAULT_VALUE; +} + +template +void +MultiThreadedSTAPLEImageFilter +::EstimatePerformanceParameters(unsigned int minExp, unsigned int maxExp) +{ + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex OutRegionIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + OutRegionIteratorType outItr(this->GetOutput(),this->GetOutput()->GetRequestedRegion()); + MaskRegionIteratorType maskItr(this->GetComputationMask(),this->GetOutput()->GetRequestedRegion()); + + for (unsigned int i = minExp; i < maxExp;++i) + { + InIteratorType inputIterator(this->GetInput(i),this->GetOutput()->GetRequestedRegion()); + std::vector > nums; + std::vector denom(m_nbClasses,0); + + inputIterator.GoToBegin(); + outItr.GoToBegin(); + maskItr.GoToBegin(); + + for (unsigned int j = 0;j < m_nbClasses;++j) + nums.push_back(std::vector (m_nbClasses,0)); + + while (!inputIterator.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + nums[j][inputIterator.Get()] += outItr.Get()[j]; + } + + ++maskItr; + ++outItr; + ++inputIterator; + } + + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + denom[j] += nums[j][k]; + + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + m_ExpParams[i](k,j) = nums[j][k]/denom[j]; + } +} + +template +void +MultiThreadedSTAPLEImageFilter +::EstimateMAPPerformanceParameters(unsigned int minExp, unsigned int maxExp) +{ + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex OutRegionIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + OutRegionIteratorType outItr(this->GetOutput(),this->GetOutput()->GetRequestedRegion()); + MaskRegionIteratorType maskItr(this->GetComputationMask(),this->GetOutput()->GetRequestedRegion()); + + for (unsigned int i = minExp; i < maxExp;++i) + { + InIteratorType inputIterator(this->GetInput(i),this->GetOutput()->GetRequestedRegion()); + std::vector > nums; + std::vector denom(m_nbClasses,0); + + inputIterator.GoToBegin(); + outItr.GoToBegin(); + maskItr.GoToBegin(); + + for (unsigned int j = 0;j < m_nbClasses;++j) + nums.push_back(std::vector (m_nbClasses,0)); + + while (!inputIterator.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + nums[j][inputIterator.Get()] += outItr.Get()[j]; + } + + ++maskItr; + ++outItr; + ++inputIterator; + } + + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + denom[j] += nums[j][k]; + + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + { + if (k != j) + { + if (denom[j] != nums[j][j]) + { + m_ExpParams[i](k,j) = nums[j][k]*(m_MAPWeighting*(m_BetaMAP - 1) + denom[j] - nums[j][j])/ + ((denom[j] + m_MAPWeighting*(m_AlphaMAP + m_BetaMAP - 2))*(denom[j] - nums[j][j])); + } + else + { + if (m_nbClasses == 2) + m_ExpParams[i](k,j) = (m_MAPWeighting*(m_BetaMAP - 1) + denom[j] - nums[j][j])/ + (denom[j] + m_MAPWeighting*(m_AlphaMAP + m_BetaMAP - 2)); + else + m_ExpParams[i](k,j) = 0; + } + } + else + m_ExpParams[i](k,j) = (nums[j][k] + m_MAPWeighting*(m_AlphaMAP - 1))/ + (denom[j] + m_MAPWeighting*(m_AlphaMAP + m_BetaMAP - 2)); + } + } +} + +template +void +MultiThreadedSTAPLEImageFilter +::EstimateFullMAPPerformanceParameters(unsigned int minExp, unsigned int maxExp) +{ + typedef itk::ImageRegionConstIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex OutRegionIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + OutRegionIteratorType outItr(this->GetOutput(),this->GetOutput()->GetRequestedRegion()); + MaskRegionIteratorType maskItr(this->GetComputationMask(),this->GetOutput()->GetRequestedRegion()); + + long double mapDenom = m_MAPWeighting*((m_nbClasses - 1)*(m_AlphaMAPNonDiag + m_BetaMAPNonDiag - 2) + m_AlphaMAP + m_BetaMAP - 2); + + for (unsigned int i = minExp; i < maxExp;++i) + { + InIteratorType inputIterator(this->GetInput(i),this->GetOutput()->GetRequestedRegion()); + std::vector > nums; + std::vector denom(m_nbClasses,mapDenom); + + inputIterator.GoToBegin(); + outItr.GoToBegin(); + maskItr.GoToBegin(); + + for (unsigned int j = 0;j < m_nbClasses;++j) + nums.push_back(std::vector (m_nbClasses,0)); + + while (!inputIterator.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + for (unsigned int j = 0;j < m_nbClasses;++j) + nums[j][inputIterator.Get()] += outItr.Get()[j]; + } + + ++maskItr; + ++outItr; + ++inputIterator; + } + + for (unsigned int j = 0;j < m_nbClasses;++j) + for (unsigned int k = 0;k < m_nbClasses;++k) + denom[j] += nums[j][k]; + + for (unsigned int j = 0;j < m_nbClasses;++j) + this->ComputeFixedPointMAPEstimates(i,j,nums[j],denom[j]); + } +} + +template +void +MultiThreadedSTAPLEImageFilter +::ComputeFixedPointMAPEstimates(unsigned int numExp, unsigned int numClass, std::vector &constantNums, long double &constantDenom) +{ + std::vector newEstimates(m_nbClasses,0), oldEstimates(m_nbClasses,0); + + for (unsigned int i = 0;i < m_nbClasses;++i) + oldEstimates[i] = m_ExpParams[numExp](i,numClass); + + unsigned int curIt = 0; + long double quickDiff = 10.0; + + while (((curIt < m_MaxIterFixedPoint)&&(quickDiff > m_EpsilonFixedPoint))||(curIt == 0)) + { + curIt++; + + for (unsigned int lp = 0;lp < m_nbClasses;++lp) + { + long double variablePart = 0; + for (unsigned int np = 0;np < m_nbClasses;++np) + { + if (np != lp) + { + if (m_MAPStructures[numExp][numClass] == 1) + { + if (np != numClass) + variablePart += (m_BetaMAPNonDiag - 1) / (oldEstimates[np] - 1); + else + variablePart += (m_BetaMAP - 1) / (oldEstimates[np] - 1); + } + else + { + // Missing structure case, the diagonal term is transferred on theta_{j0l} + if (np != 0) + variablePart += (m_BetaMAPNonDiag - 1) / (oldEstimates[np] - 1); + else + variablePart += (m_BetaMAP - 1) / (oldEstimates[np] - 1); + } + } + } + + if (m_MAPStructures[numExp][numClass] == 1) + { + if (lp != numClass) + newEstimates[lp] = constantNums[lp] + m_MAPWeighting*(m_AlphaMAPNonDiag - 1); + else + newEstimates[lp] = constantNums[lp] + m_MAPWeighting*(m_AlphaMAP - 1); + } + else + { + // Missing structure case, the diagonal term is transferred on theta_{j0l} + if (lp != 0) + newEstimates[lp] = constantNums[lp] + m_MAPWeighting*(m_AlphaMAPNonDiag - 1); + else + newEstimates[lp] = constantNums[lp] + m_MAPWeighting*(m_AlphaMAP - 1); + } + + newEstimates[lp] /= (constantDenom + m_MAPWeighting*variablePart); + } + + quickDiff = 0; + + for (unsigned int lp = 0;lp < m_nbClasses;++lp) + quickDiff = std::max(quickDiff,std::abs(newEstimates[lp] - oldEstimates[lp])); + + if (quickDiff > m_EpsilonFixedPoint) + { + for (unsigned int i = 0;i < m_nbClasses;++i) + oldEstimates[i] = newEstimates[i]; + } + } + + for (unsigned int i = 0;i < m_nbClasses;++i) + m_ExpParams[numExp](i,numClass) = newEstimates[i]; +} + +template +void +MultiThreadedSTAPLEImageFilter +::PrintSelf(std::ostream& os, itk::Indent indent) const +{ + os << indent << "Expert parameters:" << std::endl; + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + os << indent << "Expert " << i << " : " << std::endl; + for (unsigned int j = 0;j < m_nbClasses;++j) + { + for (unsigned int k = 0;k < m_nbClasses;++k) + os << indent << m_ExpParams[i](j,k) << " "; + + os << indent << std::endl; + } + + os << indent << std::endl; + } + + os << indent << std::endl; + + if (m_GTPriorImage.IsNull()) + { + os << indent << "Prior values:" << std::endl; + + for (unsigned int i = 0;i < m_Prior.size();++i) + os << indent << m_Prior[i] << " "; + + os << indent << std::endl; + } +} + +template +TInputImage * +MultiThreadedSTAPLEImageFilter +::GetClassificationAsLabelMap() +{ + if (m_LabelMap) + m_LabelMap->Delete(); + + m_LabelMap = InputImageType::New(); + + m_LabelMap->Initialize(); + m_LabelMap->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + m_LabelMap->SetSpacing(this->GetInput(0)->GetSpacing()); + m_LabelMap->SetOrigin(this->GetInput(0)->GetOrigin()); + m_LabelMap->SetDirection(this->GetInput(0)->GetDirection()); + + m_LabelMap->Allocate(); + + typedef itk::ImageRegionIteratorWithIndex InIteratorType; + typedef itk::ImageRegionIteratorWithIndex OutRegionIteratorType; + typedef itk::ImageRegionIteratorWithIndex MaskRegionIteratorType; + + InputImageRegionType largestRegion = this->GetOutput()->GetLargestPossibleRegion(); + InIteratorType labelMapItr(m_LabelMap,largestRegion); + OutRegionIteratorType outItr(this->GetOutput(),largestRegion); + + MaskRegionIteratorType maskItr(this->GetComputationMask(),largestRegion); + + while(!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + { + OutputPixelType tmpVal = outItr.Get(); + unsigned int maxClass = 0; + double maxVal = tmpVal[0]; + + for (unsigned int i = 0;i < m_nbClasses;++i) + { + if (tmpVal[i] > maxVal) + { + maxVal = tmpVal[i]; + maxClass = i; + } + } + + labelMapItr.Set(maxClass); + } + else + labelMapItr.Set(0); + + ++labelMapItr; + ++outItr; + ++maskItr; + } + + return m_LabelMap; +} + +template +itk::Image * +MultiThreadedSTAPLEImageFilter +::GetExpertParametersAsImage() +{ + ParamsImageType::Pointer resVal = ParamsImageType::New(); + InputImageRegionType region; + region.SetIndex(0,0); + region.SetIndex(1,0); + region.SetIndex(2,0); + + region.SetSize(0,this->GetNumberOfInputs()); + region.SetSize(1,m_nbClasses); + region.SetSize(2,m_nbClasses); + + resVal->Initialize(); + + resVal->SetRegions (region); + resVal->Allocate(); + + InputImageIndexType tmpInd; + for (unsigned int i = 0;i < this->GetNumberOfInputs();++i) + { + tmpInd[0] = i; + for (unsigned j = 0;j < m_nbClasses;++j) + { + tmpInd[1] = j; + for (unsigned k = 0;k < m_nbClasses;++k) + { + tmpInd[2] = k; + resVal->SetPixel(tmpInd,m_ExpParams[i](j,k)); + } + } + } + + resVal->Register(); + + return resVal; +} + +} // end namespace anima From 85853314c2563946723d4ac3ddfa625c5ca833cb Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 14:49:30 +0200 Subject: [PATCH 10/17] fusion of quantitative-mri --- .../estimation/CMakeLists.txt | 1 + .../animaCombinedRelaxometryCostFunction.cxx | 69 ++ .../animaCombinedRelaxometryCostFunction.h | 103 +++ .../CMakeLists.txt | 39 ++ .../animaCombinedRelaxometryEstimation.cxx | 218 +++++++ ...CombinedRelaxometryEstimationImageFilter.h | 150 +++++ ...mbinedRelaxometryEstimationImageFilter.hxx | 601 ++++++++++++++++++ 7 files changed, 1181 insertions(+) create mode 100644 Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.cxx create mode 100644 Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.h create mode 100644 Anima/quantitative-mri/estimation/combined_relaxometry_estimation/CMakeLists.txt create mode 100644 Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimation.cxx create mode 100644 Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.h create mode 100644 Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.hxx diff --git a/Anima/quantitative-mri/estimation/CMakeLists.txt b/Anima/quantitative-mri/estimation/CMakeLists.txt index 08dfc78e6..7834548af 100644 --- a/Anima/quantitative-mri/estimation/CMakeLists.txt +++ b/Anima/quantitative-mri/estimation/CMakeLists.txt @@ -49,6 +49,7 @@ set_lib_install_rules(${PROJECT_NAME}) ## Subdirs executables ## ############################################################################# +add_subdirectory(combined_relaxometry_estimation) add_subdirectory(gamma_mixture_t2_estimation) add_subdirectory(gmm_t2_estimation) add_subdirectory(multi_t2_estimation) diff --git a/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.cxx b/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.cxx new file mode 100644 index 000000000..f63d58727 --- /dev/null +++ b/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.cxx @@ -0,0 +1,69 @@ +#include "animaCombinedRelaxometryCostFunction.h" +#include +#include + +namespace anima +{ + +CombinedRelaxometryCostFunction::MeasureType +CombinedRelaxometryCostFunction::GetValue(const ParametersType & parameters) const +{ + switch (m_OptimizedValue) + { + case T1: + m_T1Value = parameters[0]; + break; + + case T2: + m_T2Value = parameters[0]; + break; + + case B1: + m_B1Value = parameters[0]; + break; + + case B1_Additive: + m_B1T2AdditiveValue = parameters[0]; + break; + + case M0: + default: + m_M0Value = parameters[0]; + break; + } + + double residualValue = 0; + unsigned int numT2Signals = m_T2RelaxometrySignals.size(); + double b1T2Value = std::max(0.0,m_B1T2AdditiveValue + m_B1Value); + + if (m_OptimizedValue != T2) + { + double expTRT1Value = std::exp(- m_TRValue / m_T1Value); + double baseSimulatedT1Value = m_KValue * m_M0Value * (1.0 - expTRT1Value); + + for (unsigned int i = 0;i < m_T1FlipAngles.size();++i) + { + double simulatedValue = baseSimulatedT1Value * std::sin(m_B1Value * m_T1FlipAngles[i]) / (1.0 - expTRT1Value * std::cos(m_B1Value * m_T1FlipAngles[i])); + + residualValue += (simulatedValue - m_T1RelaxometrySignals[i]) * (simulatedValue - m_T1RelaxometrySignals[i]); + } + } + + if (m_T2RelaxometrySignals.size() > 0) + { + anima::EPGSignalSimulator t2SignalSimulator; + t2SignalSimulator.SetNumberOfEchoes(m_T2RelaxometrySignals.size()); + t2SignalSimulator.SetEchoSpacing(m_T2EchoSpacing); + t2SignalSimulator.SetExcitationFlipAngle(m_T2ExcitationFlipAngle); + + anima::EPGSignalSimulator::RealVectorType simulatedT2Values = t2SignalSimulator.GetValue(m_T1Value,m_T2Value,b1T2Value * m_T2FlipAngles[0],m_M0Value); + + // Loop on all signals to be generated + for (unsigned int i = 0;i < numT2Signals;++i) + residualValue += (simulatedT2Values[i] - m_T2RelaxometrySignals[i]) * (simulatedT2Values[i] - m_T2RelaxometrySignals[i]); + } + + return residualValue; +} + +} // end of namespace anima diff --git a/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.h b/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.h new file mode 100644 index 000000000..000f52d8f --- /dev/null +++ b/Anima/quantitative-mri/estimation/animaCombinedRelaxometryCostFunction.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include "AnimaRelaxometryExport.h" + +namespace anima +{ + +class ANIMARELAXOMETRY_EXPORT CombinedRelaxometryCostFunction : +public itk::SingleValuedCostFunction +{ +public: + /** Standard class typedefs. */ + typedef CombinedRelaxometryCostFunction Self; + typedef itk::SingleValuedCostFunction Superclass; + typedef itk::SmartPointer Pointer; + typedef itk::SmartPointer ConstPointer; + + itkNewMacro(Self) + + /** Run-time type information (and related methods). */ + itkTypeMacro(CombinedRelaxometryCostFunction, Superclass) + + typedef Superclass::MeasureType MeasureType; + typedef Superclass::DerivativeType DerivativeType; + typedef Superclass::ParametersType ParametersType; + typedef std::vector < std::complex > ComplexVectorType; + typedef std::vector MatrixType; + + virtual MeasureType GetValue(const ParametersType & parameters) const ITK_OVERRIDE; + virtual void GetDerivative(const ParametersType & parameters, DerivativeType & derivative) const ITK_OVERRIDE {} // No derivative + + itkSetMacro(T2EchoSpacing, double) + itkSetMacro(T2ExcitationFlipAngle, double) + + void SetT1RelaxometrySignals(std::vector & relaxoSignals) {m_T1RelaxometrySignals = relaxoSignals;} + void SetT2RelaxometrySignals(std::vector & relaxoSignals) {m_T2RelaxometrySignals = relaxoSignals;} + + void SetT1FlipAngles(std::vector & flipAngles) {m_T1FlipAngles = flipAngles;} + void SetT2FlipAngles(std::vector & flipAngles) {m_T2FlipAngles = flipAngles;} + + itkSetMacro(TRValue, double) + itkSetMacro(KValue, double) + itkSetMacro(T1Value, double) + itkSetMacro(T2Value, double) + itkSetMacro(B1Value, double) + itkSetMacro(B1T2AdditiveValue, double) + itkSetMacro(M0Value, double) + + enum OptimizedValueType + { + M0 = 0, + B1, + B1_Additive, + T2, + T1 + }; + + itkSetMacro(OptimizedValue, OptimizedValueType) + + unsigned int GetNumberOfParameters() const ITK_OVERRIDE + { + return 1; + } + +protected: + CombinedRelaxometryCostFunction() + { + m_KValue = 1; + m_T1Value = 1; + m_T2Value = 1; + m_B1Value = 1; + m_B1T2AdditiveValue = 0; + m_M0Value = 1; + + m_OptimizedValue = T1; + + m_TRValue = 15; + m_T2EchoSpacing = 1; + } + + virtual ~CombinedRelaxometryCostFunction() {} + +private: + CombinedRelaxometryCostFunction(const Self&); //purposely not implemented + void operator=(const Self&); //purposely not implemented + + double m_T2EchoSpacing; + std::vector m_T1RelaxometrySignals; + std::vector m_T2RelaxometrySignals; + + double m_T2ExcitationFlipAngle; + std::vector m_T1FlipAngles; + std::vector m_T2FlipAngles; + + double m_TRValue; + double m_KValue; + mutable double m_T1Value, m_T2Value, m_M0Value, m_B1Value, m_B1T2AdditiveValue; + + OptimizedValueType m_OptimizedValue; +}; + +} // end namespace anima diff --git a/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/CMakeLists.txt b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/CMakeLists.txt new file mode 100644 index 000000000..bc568ec38 --- /dev/null +++ b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/CMakeLists.txt @@ -0,0 +1,39 @@ +if(BUILD_TOOLS) + +project(animaCombinedRelaxometryEstimation) + +## ############################################################################# +## 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} + AnimaOptimizers + ${NLOPT_LIBRARY} + AnimaRelaxometry + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimation.cxx b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimation.cxx new file mode 100644 index 000000000..ad8a975a4 --- /dev/null +++ b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimation.cxx @@ -0,0 +1,218 @@ +#include + +#include +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + TCLAP::ValueArg t1Arg("","t1","List of T1 relaxometry images",true,"","T1 relaxometry images",cmd); + TCLAP::ValueArg t2Arg("","t2","List of T2 relaxometry images",true,"","T2 relaxometry images",cmd); + TCLAP::ValueArg maskArg("m","maskname","Computation mask",false,"","computation mask",cmd); + + TCLAP::ValueArg resT1Arg("","out-t1","Result T1 image",true,"","result T1 image",cmd); + TCLAP::ValueArg resT2Arg("","out-t2","Result T2 image",false,"","result T2 image",cmd); + TCLAP::ValueArg resT1M0Arg("","out-m0-t1","Result M0 image (for T1 image)",false,"","result T1 M0 image",cmd); + TCLAP::ValueArg resT2M0Arg("","out-m0-t2","Result M0 image (for T2 image)",false,"","result T2 M0 image",cmd); + TCLAP::ValueArg resB1Arg("","out-b1","Result B1 image",false,"","result B1 image",cmd); + TCLAP::ValueArg resB1AddArg("","out-b1-add","Result B1 additive image",false,"","result B1 additive image",cmd); + + TCLAP::ValueArg trT1Arg("","tr-t1","Repetition time for T1 relaxometry (default: 5000)",false,5000,"T1 repetition time",cmd); + TCLAP::ValueArg t1FlipAnglesArg("","t1-flip","Text file listing flip angles for T1 (in degrees)",true,"","T1 flip angles list",cmd); + + TCLAP::ValueArg trT2Arg("","tr-t2","Repetition time for T2 relaxometry (default: 5000)",false,5000,"T2 repetition time",cmd); + TCLAP::ValueArg t2EchoSpacingArg("e","t2-echo-spacing","Echo spacing between T2 relaxometry images (default: 50)",false,50,"T2 echo spacing",cmd); + TCLAP::ValueArg excitationT2FlipAngleArg("","t2-ex-flip","Excitation flip angle for T2 (in degrees, default: 90)",false,90,"T2 excitation flip angle",cmd); + TCLAP::ValueArg t2FlipAngleArg("","t2-flip","All flip angles for T2 (in degrees, default: 180)",false,180,"T2 flip angle",cmd); + + TCLAP::ValueArg upperBoundT1Arg("","upper-bound-t1","T1 value upper bound (default: 5000)",false,5000,"T1 upper bound",cmd); + TCLAP::ValueArg upperBoundT2Arg("","upper-bound-t2","T2 value upper bound (default: 1000)",false,1000,"T2 upper bound",cmd); + TCLAP::ValueArg upperBoundM0Arg("","upper-bound-m0","M0 value upper bound (default: 5000)",false,5000,"M0 upper bound",cmd); + TCLAP::ValueArg lowerBoundB1Arg("","lower-bound-b1","B1 value lower bound (default: 0.2)",false,0.2,"B1 lower bound",cmd); + TCLAP::ValueArg upperBoundB1Arg("","upper-bound-b1","B1 value upper bound (default: 2)",false,2,"B1 upper bound",cmd); + + TCLAP::SwitchArg kMestimateSigmaArg("k","k-mest","M-estimation for K factor (experimental and apparently buggy)",cmd, false); + TCLAP::ValueArg b1SmoothingSigmaArg("s","b1-sigma","B1 smoothing sigma (in pixels, default: 3)",false,3,"B1 smoothing sigma",cmd); + TCLAP::ValueArg backgroundSignalThresholdArg("t","signal-thr","Background signal threshold (default: 100)",false,100,"Background signal threshold",cmd); + + TCLAP::ValueArg numIterArg("n","num-iter","Number of optimization loops (default: 100)",false,100,"Number of iteration loops",cmd); + TCLAP::ValueArg numOptimizerIterArg("","opt-iter","Maximal number of optimizer iterations (default: 200)",false,200,"Maximal number of optimizer iterations",cmd); + TCLAP::ValueArg optimizerStopConditionArg("","opt-stop","Optimizer stopping threshold (default: 1.0e-4)",false,1.0e-4,"Optimizer stopping threshold",cmd); + + TCLAP::ValueArg nbpArg("T","numberofthreads","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; + } + + typedef itk::Image InputImageType; + typedef itk::Image Image4DType; + typedef InputImageType OutputImageType; + typedef anima::CombinedRelaxometryEstimationImageFilter FilterType; + + FilterType::Pointer mainFilter = FilterType::New(); + + // Read and set T1 relaxometry images + bool readingFail = false; + itk::ImageIOBase::Pointer imageIO = itk::ImageIOFactory::CreateImageIO(t1Arg.getValue().c_str(), + itk::IOFileModeEnum::ReadMode); + + if (!imageIO) + readingFail = true; + + if (readingFail) + { + std::ifstream fileIn(t1Arg.getValue().c_str()); + if (!fileIn.is_open()) + { + std::cerr << "Unable to read file: " << t1Arg.getValue() << std::endl; + return EXIT_FAILURE; + } + + while (!fileIn.eof()) + { + char tmpStr[2048]; + fileIn.getline(tmpStr,2048); + + if (strcmp(tmpStr,"") == 0) + continue; + + mainFilter->AddT1RelaxometryInput(anima::readImage (tmpStr)); + } + + fileIn.close(); + } + else + { + imageIO->SetFileName(t1Arg.getValue()); + imageIO->ReadImageInformation(); + + // Do we have a (N+1)D image ? If not, exiting, otherwise considering the last dimension as each input + unsigned int ndim = imageIO->GetNumberOfDimensions(); + + if (ndim != (InputImageType::ImageDimension + 1)) + { + std::cerr << "Unable to read file: " << t1Arg.getValue() << std::endl; + return EXIT_FAILURE; + } + + std::vector inputData; + inputData = anima::getImagesFromHigherDimensionImage(anima::readImage (t1Arg.getValue())); + + for (unsigned int i = 0;i < inputData.size();++i) + mainFilter->AddT1RelaxometryInput(inputData[i]); + } + + std::ifstream inputT1Flip(t1FlipAnglesArg.getValue().c_str()); + if (!inputT1Flip.is_open()) + { + std::cerr << "Text file for T1 flip angles not readable " << t1FlipAnglesArg.getValue() << std::endl; + return EXIT_FAILURE; + } + + std::vector t1FlipAngles; + while (!inputT1Flip.eof()) + { + char tmpStr[8192]; + inputT1Flip.getline(tmpStr,8192); + std::string workStr(tmpStr); + if (workStr == "") + continue; + + workStr.erase(workStr.find_last_not_of(" \n\r\t")+1); + std::istringstream iss(workStr); + std::string shortStr; + iss >> shortStr; + t1FlipAngles.push_back(std::stod(shortStr) * M_PI / 180.0); + } + + mainFilter->SetT1FlipAngles(t1FlipAngles); + mainFilter->SetTRT1Value(trT1Arg.getValue()); + + // Read and set T2 relaxometry images + unsigned int numT2Inputs = anima::setMultipleImageFilterInputsFromFileName(t2Arg.getValue(),mainFilter); + + mainFilter->SetT2FlipAngles(t2FlipAngleArg.getValue() * M_PI / 180.0,numT2Inputs); + mainFilter->SetT2ExcitationFlipAngle(excitationT2FlipAngleArg.getValue() * M_PI / 180.0); + mainFilter->SetT2EchoSpacing(t2EchoSpacingArg.getValue()); + mainFilter->SetTRT2Value(trT2Arg.getValue()); + mainFilter->SetKFactorMEstimation(kMestimateSigmaArg.isSet()); + + mainFilter->SetM0UpperBound(upperBoundM0Arg.getValue()); + mainFilter->SetT1UpperBound(upperBoundT1Arg.getValue()); + mainFilter->SetT2UpperBound(upperBoundT2Arg.getValue()); + mainFilter->SetB1LowerBound(lowerBoundB1Arg.getValue()); + mainFilter->SetB1UpperBound(upperBoundB1Arg.getValue()); + + if (maskArg.getValue() != "") + { + typedef itk::ImageFileReader < itk::Image > itkMaskReader; + itkMaskReader::Pointer maskRead = itkMaskReader::New(); + maskRead->SetFileName(maskArg.getValue().c_str()); + maskRead->Update(); + + mainFilter->SetComputationMask(maskRead->GetOutput()); + } + + mainFilter->SetAverageSignalThreshold(backgroundSignalThresholdArg.getValue()); + mainFilter->SetB1SmoothingSigma(b1SmoothingSigmaArg.getValue()); + + mainFilter->SetNumberOfIterations(numIterArg.getValue()); + mainFilter->SetMaximumOptimizerIterations(numOptimizerIterArg.getValue()); + mainFilter->SetOptimizerStopCondition(optimizerStopConditionArg.getValue()); + + mainFilter->SetNumberOfWorkUnits(nbpArg.getValue()); + + itk::TimeProbe tmpTime; + tmpTime.Start(); + + mainFilter->Update(); + + tmpTime.Stop(); + + std::cout << "Total computation time: " << tmpTime.GetTotal() << std::endl; + + anima::writeImage (resT1Arg.getValue(),mainFilter->GetOutput(0)); + + if (resT2Arg.getValue() != "") + anima::writeImage (resT2Arg.getValue(),mainFilter->GetOutput(1)); + + if (resT2M0Arg.getValue() != "") + anima::writeImage (resT2M0Arg.getValue(),mainFilter->GetOutput(2)); + + if (resT1M0Arg.getValue() != "") + { + double kFactor = mainFilter->GetKFactorM0(); + + itk::ImageRegionIterator outputM0Iterator (mainFilter->GetOutput(2),mainFilter->GetOutput(2)->GetLargestPossibleRegion()); + + while (!outputM0Iterator.IsAtEnd()) + { + outputM0Iterator.Set(outputM0Iterator.Get() * kFactor); + ++outputM0Iterator; + } + + anima::writeImage (resT1M0Arg.getValue(),mainFilter->GetOutput(2)); + } + + if (resB1Arg.getValue() != "") + anima::writeImage (resB1Arg.getValue(),mainFilter->GetOutput(3)); + + if (resB1AddArg.getValue() != "") + anima::writeImage (resB1AddArg.getValue(),mainFilter->GetOutput(4)); + + std::cout << "Computed k factor between T2 and T1 M0 values: " << mainFilter->GetKFactorM0() << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.h b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.h new file mode 100644 index 000000000..71413701c --- /dev/null +++ b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include + +#include + +namespace anima +{ + +template +class CombinedRelaxometryEstimationImageFilter : +public anima::MaskedImageToImageFilter +{ +public: + /** Standard class typedefs. */ + typedef CombinedRelaxometryEstimationImageFilter Self; + typedef anima::MaskedImageToImageFilter< 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(CombinedRelaxometryEstimationImageFilter, MaskedImageToImageFilter) + + /** Image typedef support */ + typedef TInputImage InputImageType; + typedef TOutputImage OutputImageType; + typedef typename InputImageType::Pointer InputImagePointer; + typedef typename OutputImageType::Pointer OutputImagePointer; + + typedef itk::SingleValuedCostFunction BaseCostFunctionType; + typedef BaseCostFunctionType::ParametersType ParametersType; + + /** Superclass typedefs. */ + typedef typename Superclass::MaskImageType MaskImageType; + typedef typename Superclass::InputImageRegionType InputImageRegionType; + typedef typename Superclass::OutputImageRegionType OutputImageRegionType; + typedef typename Superclass::ThreadStruct ThreadStruct; + + itkSetMacro(TRT1Value, double) + itkSetMacro(TRT2Value, double) + + itkGetMacro(KFactorM0, double) + itkSetMacro(KFactorMEstimation, double) + + itkSetMacro(T2EchoSpacing, double) + itkSetMacro(T2ExcitationFlipAngle, double) + + itkSetMacro(B1SmoothingSigma, double) + itkSetMacro(AverageSignalThreshold, double) + + itkSetMacro(NumberOfIterations, unsigned int) + itkSetMacro(MaximumOptimizerIterations, unsigned int) + itkSetMacro(OptimizerStopCondition, double) + + itkSetMacro(M0UpperBound, double) + itkSetMacro(T1UpperBound, double) + itkSetMacro(T2UpperBound, double) + + itkSetMacro(B1LowerBound, double) + itkSetMacro(B1UpperBound, double) + + void AddT1RelaxometryInput(TInputImage *image); + + void SetT1FlipAngles(double singleAngle, unsigned int numAngles) {m_T1FlipAngles = std::vector (numAngles,singleAngle);} + void SetT2FlipAngles(double singleAngle, unsigned int numAngles) {m_T2FlipAngles = std::vector (numAngles,singleAngle);} + + void SetT1FlipAngles(std::vector &flipAngles) {m_T1FlipAngles = flipAngles;} + void SetT2FlipAngles(std::vector &flipAngles) {m_T2FlipAngles = flipAngles;} + +protected: + CombinedRelaxometryEstimationImageFilter() + : Superclass() + { + // There are 4 outputs: T1, T2, M0, B1, B1_Additive + this->SetNumberOfRequiredOutputs(5); + + for (unsigned int i = 0;i < 5;++i) + this->SetNthOutput(i, this->MakeOutput(i)); + + m_AverageSignalThreshold = 0; + m_T2EchoSpacing = 1; + m_T2ExcitationFlipAngle = M_PI / 6; + + m_KFactorMEstimation = false; + + m_TRT1Value = 1; + m_TRT2Value = 1; + m_KFactorM0 = 1; + m_B1SmoothingSigma = 2; + + m_NumberOfIterations = 200; + m_MaximumOptimizerIterations = 200; + m_OptimizerStopCondition = 1.0e-4; + + m_M0UpperBound = 5000; + m_T1UpperBound = 5000; + m_T2UpperBound = 1000; + m_B1LowerBound = 0.2; + m_B1UpperBound = 2; + } + + virtual ~CombinedRelaxometryEstimationImageFilter() {} + + void CheckComputationMask() ITK_OVERRIDE; + void GenerateData() ITK_OVERRIDE; + + void DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) ITK_OVERRIDE; + + void InitializeOutputs(); + + void UpdateT1KFactorM0(); + void SmoothB1Field(); + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(CombinedRelaxometryEstimationImageFilter); + + double m_AverageSignalThreshold; + double m_B1SmoothingSigma; + + unsigned int m_MaximumOptimizerIterations; + double m_OptimizerStopCondition; + + unsigned int m_NumberOfIterations; + + // T1 relaxometry specific values + double m_TRT1Value; + std::vector m_T1RelaxometryInputs; + std::vector m_T1FlipAngles; + + double m_KFactorM0; + bool m_KFactorMEstimation; + + // T2 relaxometry specific values + double m_TRT2Value; + double m_T2EchoSpacing; + std::vector m_T2FlipAngles; + double m_T2ExcitationFlipAngle; + + double m_M0UpperBound, m_T1UpperBound, m_T2UpperBound; + double m_B1LowerBound, m_B1UpperBound; +}; + +} // end namespace anima + +#include "animaCombinedRelaxometryEstimationImageFilter.hxx" diff --git a/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.hxx b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.hxx new file mode 100644 index 000000000..452aee21b --- /dev/null +++ b/Anima/quantitative-mri/estimation/combined_relaxometry_estimation/animaCombinedRelaxometryEstimationImageFilter.hxx @@ -0,0 +1,601 @@ +#pragma once +#include "animaCombinedRelaxometryEstimationImageFilter.h" + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace anima +{ + +template +void +CombinedRelaxometryEstimationImageFilter +::AddT1RelaxometryInput(TInputImage *image) +{ + m_T1RelaxometryInputs.push_back(image); +} + +template +void +CombinedRelaxometryEstimationImageFilter +::CheckComputationMask() +{ + if (this->GetComputationMask()) + return; + + typedef itk::ImageRegionConstIterator IteratorType; + typedef itk::ImageRegionIterator MaskIteratorType; + + std::vector inT2Itrs(this->GetNumberOfIndexedInputs()); + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + inT2Itrs[i] = IteratorType(this->GetInput(i),this->GetOutput()->GetLargestPossibleRegion()); + + std::vector inT1Itrs(m_T1RelaxometryInputs.size()); + for (unsigned int i = 0;i < m_T1RelaxometryInputs.size();++i) + inT1Itrs[i] = IteratorType(m_T1RelaxometryInputs[i],this->GetOutput()->GetLargestPossibleRegion()); + + typename MaskImageType::Pointer maskImage = MaskImageType::New(); + maskImage->Initialize(); + maskImage->SetRegions(this->GetInput(0)->GetLargestPossibleRegion()); + maskImage->SetSpacing (this->GetInput(0)->GetSpacing()); + maskImage->SetOrigin (this->GetInput(0)->GetOrigin()); + maskImage->SetDirection (this->GetInput(0)->GetDirection()); + maskImage->Allocate(); + + MaskIteratorType maskItr (maskImage,this->GetOutput()->GetLargestPossibleRegion()); + while (!maskItr.IsAtEnd()) + { + double averageVal = 0; + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + averageVal += inT2Itrs[i].Get(); + + averageVal /= this->GetNumberOfIndexedInputs(); + + bool maskPoint = (averageVal <= m_AverageSignalThreshold); + + averageVal = 0; + for (unsigned int i = 0;i < m_T1RelaxometryInputs.size();++i) + averageVal += inT1Itrs[i].Get(); + + averageVal /= m_T1RelaxometryInputs.size(); + maskPoint = maskPoint || (averageVal <= m_AverageSignalThreshold); + + if (maskPoint) + maskItr.Set(0); + else + maskItr.Set(1); + + for (unsigned int i = 0;i < m_T1RelaxometryInputs.size();++i) + ++inT1Itrs[i]; + + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + ++inT2Itrs[i]; + + ++maskItr; + } + + this->SetComputationMask(maskImage); +} + +template +void +CombinedRelaxometryEstimationImageFilter +::GenerateData() +{ + this->AllocateOutputs(); + this->BeforeThreadedGenerateData(); + + // Here comes the main part now + this->GetOutput(0)->FillBuffer(0); + this->GetOutput(1)->FillBuffer(0); + this->GetOutput(2)->FillBuffer(0); + this->GetOutput(3)->FillBuffer(1); + this->GetOutput(4)->FillBuffer(0); + + // Compute initial values for all outputs + // Threaded initialization + this->InitializeOutputs(); + + // Loop starts here + for (unsigned int i = 0;i < m_NumberOfIterations;++i) + { + itk::TimeProbe tmpTimer; + tmpTimer.Start(); + + std::cout << "Performing iteration " << i << "... " << std::flush; + + // Estimate k factor for M0 + this->UpdateT1KFactorM0(); + + // Then call alternate optimization threaded part + this->GetMultiThreader()->template ParallelizeImageRegion ( + this->GetOutput()->GetRequestedRegion(), + [this](const OutputImageRegionType & outputRegionForThread) + { this->DynamicThreadedGenerateData(outputRegionForThread); }, this); + + // Then smooth B1 map + if (m_B1SmoothingSigma > 0) + this->SmoothB1Field(); + + tmpTimer.Stop(); + + std::cout << "Done in " << tmpTimer.GetTotal() << "s" << std::endl; + } +} + +template +void +CombinedRelaxometryEstimationImageFilter +::InitializeOutputs() +{ + typedef anima::T1RelaxometryEstimationImageFilter T1EstimatorType; + + // T1 estimation + typename T1EstimatorType::Pointer t1Estimator = T1EstimatorType::New(); + t1Estimator->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + t1Estimator->SetInput(0,m_T1RelaxometryInputs[0]); + t1Estimator->SetInput(1,m_T1RelaxometryInputs[m_T1RelaxometryInputs.size()-1]); + t1Estimator->SetTRValue(m_TRT1Value); + t1Estimator->SetT1UpperBoundValue(m_T1UpperBound); + t1Estimator->SetM0UpperBoundValue(m_M0UpperBound); + t1Estimator->SetComputationMask(this->GetComputationMask()); + t1Estimator->SetFlipAngles(m_T1FlipAngles); + + t1Estimator->Update(); + + OutputImagePointer tmpOutput = t1Estimator->GetOutput(0); + tmpOutput->DisconnectPipeline(); + + this->SetNthOutput(0,tmpOutput); + + // T2 estimation + typedef anima::T2RelaxometryEstimationImageFilter T2EstimatorType; + + typename T2EstimatorType::Pointer t2Estimator = T2EstimatorType::New(); + t2Estimator->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + for (unsigned int i = 0;i < this->GetNumberOfIndexedInputs();++i) + t2Estimator->SetInput(i,this->GetInput(i)); + + t2Estimator->SetTRValue(m_TRT2Value); + t2Estimator->SetT2UpperBoundValue(m_T2UpperBound); + t2Estimator->SetComputationMask(this->GetComputationMask()); + + t2Estimator->SetEchoSpacing(m_T2EchoSpacing); + t2Estimator->SetT1Map(this->GetOutput(0)); + + t2Estimator->Update(); + + tmpOutput = t2Estimator->GetOutput(0); + tmpOutput->DisconnectPipeline(); + this->SetNthOutput(1,tmpOutput); + + tmpOutput = t2Estimator->GetOutput(1); + tmpOutput->DisconnectPipeline(); + this->SetNthOutput(2,tmpOutput); + + // Now set values of B1 map that are in mask to 1, in case they are no; + typedef itk::ImageRegionIterator OutImageIteratorType; + OutImageIteratorType b1Itr(this->GetOutput(3),this->GetOutput(0)->GetLargestPossibleRegion()); + + typedef itk::ImageRegionIterator MaskIteratorType; + MaskIteratorType maskItr(this->GetComputationMask(),this->GetOutput(0)->GetLargestPossibleRegion()); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() != 0) + b1Itr.Set(1); + + ++b1Itr; + ++maskItr; + } +} + +template +void +CombinedRelaxometryEstimationImageFilter +::UpdateT1KFactorM0() +{ + typedef itk::ImageRegionConstIterator ImageIteratorType; + + unsigned int numT1Inputs = m_T1RelaxometryInputs.size(); + std::vector inT1Iterators; + for (unsigned int i = 0;i < numT1Inputs;++i) + inT1Iterators.push_back(ImageIteratorType(m_T1RelaxometryInputs[i],this->GetOutput(0)->GetLargestPossibleRegion())); + + typedef itk::ImageRegionIterator OutImageIteratorType; + OutImageIteratorType outT1Iterator(this->GetOutput(0),this->GetOutput(0)->GetLargestPossibleRegion()); + OutImageIteratorType outM0Iterator(this->GetOutput(2),this->GetOutput(0)->GetLargestPossibleRegion()); + OutImageIteratorType outB1Iterator(this->GetOutput(3),this->GetOutput(0)->GetLargestPossibleRegion()); + + typedef itk::ImageRegionIterator MaskIteratorType; + MaskIteratorType maskItr(this->GetComputationMask(),this->GetOutput(0)->GetLargestPossibleRegion()); + + std::vector trueValues, simulatedValues; + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + for (unsigned int i = 0;i < numT1Inputs;++i) + ++inT1Iterators[i]; + + ++maskItr; + ++outT1Iterator; + ++outM0Iterator; + ++outB1Iterator; + + continue; + } + + double b1Value = outB1Iterator.Get(); + double t1Value = outT1Iterator.Get(); + double m0Value = outM0Iterator.Get(); + + for (unsigned int i = 0;i < numT1Inputs;++i) + { + double simulatedValue = m0Value * (1.0 - std::exp(- m_TRT1Value / t1Value)) * std::sin(b1Value * m_T1FlipAngles[i]); + simulatedValue /= 1.0 - std::exp(- m_TRT1Value / t1Value) * std::cos(b1Value * m_T1FlipAngles[i]); + + simulatedValues.push_back(simulatedValue); + trueValues.push_back(inT1Iterators[i].Get()); + } + + for (unsigned int i = 0;i < numT1Inputs;++i) + ++inT1Iterators[i]; + + ++maskItr; + ++outT1Iterator; + ++outM0Iterator; + ++outB1Iterator; + } + + unsigned int numValues = simulatedValues.size(); + + unsigned int numIter = 0; + unsigned int numMaxIter = 100; + + std::vector residuals(numValues,0.0); + std::vector weights(numValues,1.0); + std::vector leverageValues(numValues,0.0); + + double newKValue = 1.0; + double oldKValue = 0; + + // Compute leverage values for robust fit (cf matlab robustfit) + double meanXValue = 0; + for (unsigned int i = 0;i < numValues;++i) + meanXValue += simulatedValues[i]; + meanXValue /= numValues; + + double sumDiffSquared = 0; + for (unsigned int i = 0;i < numValues;++i) + { + leverageValues[i] = (simulatedValues[i] - meanXValue) * (simulatedValues[i] - meanXValue); + sumDiffSquared += leverageValues[i]; + } + + for (unsigned int i = 0;i < numValues;++i) + leverageValues[i] /= sumDiffSquared; + + while (numIter < numMaxIter) + { + ++numIter; + + oldKValue = newKValue; + + double numeratorValue = 0; + double denominatorValue = 0; + + for (unsigned int i = 0;i < numValues;++i) + { + numeratorValue += weights[i] * trueValues[i] * simulatedValues[i]; + denominatorValue += weights[i] * simulatedValues[i] * simulatedValues[i]; + } + + newKValue = numeratorValue / denominatorValue; + + if ((std::abs(newKValue - oldKValue) < 1.0e-4)||(!m_KFactorMEstimation)) + break; + + for (unsigned int i = 0;i < numValues;++i) + residuals[i] = trueValues[i] - newKValue * simulatedValues[i]; + + std::vector sortedResiduals = residuals; + unsigned int posMedian = floor(numValues / 2.0); + std::partial_sort(sortedResiduals.begin(),sortedResiduals.begin() + posMedian + 1,sortedResiduals.end()); + + double medianResidual = sortedResiduals[posMedian]; + for (unsigned int i = 0;i < numValues;++i) + sortedResiduals[i] = std::abs(sortedResiduals[i] - medianResidual); + std::partial_sort(sortedResiduals.begin(),sortedResiduals.begin() + posMedian + 1,sortedResiduals.end()); + + double robustDeviation = sortedResiduals[posMedian] / 0.6745; + + for (unsigned int i = 0;i < numValues;++i) + residuals[i] /= 4.685 * robustDeviation * std::sqrt(1 - leverageValues[i]); + + // Tukey bi-square as in matlab robust-fit + for (unsigned int i = 0;i < numValues;++i) + { + weights[i] = 0; + + if (std::abs(residuals[i]) < 1.0) + weights[i] = (1 - residuals[i] * residuals[i]) * (1 - residuals[i] * residuals[i]); + } + } + + m_KFactorM0 = newKValue; +} + +template +void +CombinedRelaxometryEstimationImageFilter +::DynamicThreadedGenerateData(const OutputImageRegionType &outputRegionForThread) +{ + typedef itk::ImageRegionConstIterator ImageIteratorType; + + unsigned int numT1Inputs = m_T1RelaxometryInputs.size(); + std::vector inT1Iterators; + for (unsigned int i = 0;i < numT1Inputs;++i) + inT1Iterators.push_back(ImageIteratorType(m_T1RelaxometryInputs[i],outputRegionForThread)); + + unsigned int numT2Inputs = this->GetNumberOfIndexedInputs(); + std::vector inT2Iterators; + for (unsigned int i = 0;i < numT2Inputs;++i) + inT2Iterators.push_back(ImageIteratorType(this->GetInput(i),outputRegionForThread)); + + typedef itk::ImageRegionIterator OutImageIteratorType; + OutImageIteratorType outT1Iterator(this->GetOutput(0),outputRegionForThread); + OutImageIteratorType outT2Iterator(this->GetOutput(1),outputRegionForThread); + OutImageIteratorType outM0Iterator(this->GetOutput(2),outputRegionForThread); + OutImageIteratorType outB1Iterator(this->GetOutput(3),outputRegionForThread); + OutImageIteratorType outB1AdditiveIterator(this->GetOutput(4),outputRegionForThread); + + typedef itk::ImageRegionIterator MaskIteratorType; + MaskIteratorType maskItr(this->GetComputationMask(),outputRegionForThread); + + std::vector relaxoT1Data(numT1Inputs,0); + std::vector relaxoT2Data(numT2Inputs,0); + + typedef anima::NLOPTOptimizers OptimizerType; + typedef anima::CombinedRelaxometryCostFunction CombinedCostFunctionType; + + CombinedCostFunctionType::Pointer cost = CombinedCostFunctionType::New(); + cost->SetT2EchoSpacing(m_T2EchoSpacing); + cost->SetT2ExcitationFlipAngle(m_T2ExcitationFlipAngle); + cost->SetTRValue(m_TRT1Value); + cost->SetKValue(m_KFactorM0); + cost->SetT1FlipAngles(m_T1FlipAngles); + cost->SetT2FlipAngles(m_T2FlipAngles); + + unsigned int dimension = 1; + itk::Array lowerBounds(dimension); + itk::Array upperBounds(dimension); + OptimizerType::ParametersType p(dimension); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + outT1Iterator.Set(0.0); + outT2Iterator.Set(0.0); + outM0Iterator.Set(0.0); + + for (unsigned int i = 0;i < numT1Inputs;++i) + ++inT1Iterators[i]; + + for (unsigned int i = 0;i < numT2Inputs;++i) + ++inT2Iterators[i]; + + ++maskItr; + ++outT1Iterator; + ++outT2Iterator; + ++outM0Iterator; + ++outB1Iterator; + ++outB1AdditiveIterator; + + continue; + } + + for (unsigned int i = 0;i < numT1Inputs;++i) + relaxoT1Data[i] = inT1Iterators[i].Get(); + + for (unsigned int i = 0;i < numT2Inputs;++i) + relaxoT2Data[i] = inT2Iterators[i].Get(); + + cost->SetT1RelaxometrySignals(relaxoT1Data); + cost->SetT2RelaxometrySignals(relaxoT2Data); + + // Switch over optimizations: B1, B1 additive, T2, M0, T1 + for (unsigned int i = 0;i < 5;++i) + { + cost->SetT1Value(outT1Iterator.Get()); + cost->SetT2Value(outT2Iterator.Get()); + cost->SetM0Value(outM0Iterator.Get()); + cost->SetB1Value(outB1Iterator.Get()); + cost->SetB1T2AdditiveValue(outB1AdditiveIterator.Get()); + + CombinedCostFunctionType::OptimizedValueType optValueType = (CombinedCostFunctionType::OptimizedValueType)i; + cost->SetOptimizedValue(optValueType); + + OptimizerType::Pointer optimizer = OptimizerType::New(); + optimizer->SetAlgorithm(NLOPT_LN_BOBYQA); + optimizer->SetXTolRel(m_OptimizerStopCondition); + optimizer->SetFTolRel(1.0e-2 * m_OptimizerStopCondition); + optimizer->SetMaxEval(m_MaximumOptimizerIterations); + optimizer->SetVectorStorageSize(2000); + + switch(optValueType) + { + case CombinedCostFunctionType::B1: + lowerBounds[0] = m_B1LowerBound; + upperBounds[0] = m_B1UpperBound; + p[0] = outB1Iterator.Get(); + break; + + case CombinedCostFunctionType::B1_Additive: + { + double b1Value = outB1Iterator.Get(); + p[0] = outB1AdditiveIterator.Get(); + + lowerBounds[0] = m_B1LowerBound - b1Value; + upperBounds[0] = 1.0 - b1Value; + break; + } + + case CombinedCostFunctionType::T2: + lowerBounds[0] = 1.0e-4; + upperBounds[0] = outT1Iterator.Get(); + if (upperBounds[0] > m_T2UpperBound) + upperBounds[0] = m_T2UpperBound; + p[0] = outT2Iterator.Get(); + break; + + case CombinedCostFunctionType::M0: + lowerBounds[0] = 1.0e-4; + upperBounds[0] = m_M0UpperBound; + p[0] = outM0Iterator.Get(); + break; + + case CombinedCostFunctionType::T1: + default: + lowerBounds[0] = 1.0e-4; + upperBounds[0] = m_T1UpperBound; + p[0] = outT1Iterator.Get(); + break; + } + + optimizer->SetLowerBoundParameters(lowerBounds); + optimizer->SetUpperBoundParameters(upperBounds); + optimizer->SetMaximize(false); + + optimizer->SetCostFunction(cost); + + optimizer->SetInitialPosition(p); + optimizer->StartOptimization(); + + p = optimizer->GetCurrentPosition(); + + switch(optValueType) + { + case CombinedCostFunctionType::B1: + outB1Iterator.Set(p[0]); + break; + + case CombinedCostFunctionType::B1_Additive: + outB1AdditiveIterator.Set(p[0]); + break; + + case CombinedCostFunctionType::T2: + outT2Iterator.Set(p[0]); + break; + + case CombinedCostFunctionType::M0: + outM0Iterator.Set(p[0]); + break; + + case CombinedCostFunctionType::T1: + default: + outT1Iterator.Set(p[0]); + break; + } + } + + // Update iterators for next pixel + for (unsigned int i = 0;i < numT1Inputs;++i) + ++inT1Iterators[i]; + + for (unsigned int i = 0;i < numT2Inputs;++i) + ++inT2Iterators[i]; + + ++maskItr; + ++outT1Iterator; + ++outT2Iterator; + ++outM0Iterator; + ++outB1Iterator; + ++outB1AdditiveIterator; + } +} + +template +void +CombinedRelaxometryEstimationImageFilter +::SmoothB1Field() +{ + typedef itk::MultiplyImageFilter MultiplyFilterType; + + typename MultiplyFilterType::Pointer multiplier = MultiplyFilterType::New(); + multiplier->SetInput1(this->GetOutput(3)); + multiplier->SetInput2(this->GetComputationMask()); + multiplier->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + + multiplier->Update(); + + typedef anima::SmoothingRecursiveYvvGaussianImageFilter SmoothingFilterType; + + typename SmoothingFilterType::Pointer smoother = SmoothingFilterType::New(); + smoother->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + smoother->SetInput(multiplier->GetOutput()); + + smoother->SetSigma(m_B1SmoothingSigma); + + smoother->Update(); + + typedef anima::SmoothingRecursiveYvvGaussianImageFilter SmoothingMaskFilterType; + + typename SmoothingMaskFilterType::Pointer maskSmoother = SmoothingMaskFilterType::New(); + maskSmoother->SetNumberOfWorkUnits(this->GetNumberOfWorkUnits()); + maskSmoother->SetInput(this->GetComputationMask()); + + maskSmoother->SetSigma(m_B1SmoothingSigma); + + maskSmoother->Update(); + + typename OutputImageType::Pointer tmpOutput = smoother->GetOutput(); + tmpOutput->DisconnectPipeline(); + + typedef itk::ImageRegionIterator OutImageIteratorType; + typedef itk::ImageRegionIterator MaskImageIteratorType; + + OutImageIteratorType smoothItr(tmpOutput,tmpOutput->GetLargestPossibleRegion()); + OutImageIteratorType maskSmoothItr(maskSmoother->GetOutput(),tmpOutput->GetLargestPossibleRegion()); + MaskImageIteratorType maskItr(this->GetComputationMask(),this->GetComputationMask()->GetLargestPossibleRegion()); + + while (!maskItr.IsAtEnd()) + { + if (maskItr.Get() == 0) + { + smoothItr.Set(1); + + ++smoothItr; + ++maskItr; + ++maskSmoothItr; + continue; + } + + double smoothVal = 1; + if (maskSmoothItr.Get() != 0) + smoothVal = smoothItr.Get() / maskSmoothItr.Get(); + + smoothItr.Set(smoothVal); + + ++smoothItr; + ++maskItr; + ++maskSmoothItr; + } + + this->SetNthOutput(3,tmpOutput); +} + +} // end of namespace anima From 0d5462d35ddbfbd22f9038785965d3547b1e892f Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 14:55:45 +0200 Subject: [PATCH 11/17] fusion of registration **part 1** --- Anima/registration/resamplers/CMakeLists.txt | 1 + .../apply_vtk_transform/CMakeLists.txt | 36 ++++ .../animaApplyVtkTransform.cxx | 180 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 Anima/registration/resamplers/apply_vtk_transform/CMakeLists.txt create mode 100644 Anima/registration/resamplers/apply_vtk_transform/animaApplyVtkTransform.cxx diff --git a/Anima/registration/resamplers/CMakeLists.txt b/Anima/registration/resamplers/CMakeLists.txt index 5df1843e0..874284ab5 100644 --- a/Anima/registration/resamplers/CMakeLists.txt +++ b/Anima/registration/resamplers/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(apply_transform_serie) +add_subdirectory(apply_vtk_transform) add_subdirectory(apply_distortion_correction) if (USE_VTK AND VTK_FOUND) diff --git a/Anima/registration/resamplers/apply_vtk_transform/CMakeLists.txt b/Anima/registration/resamplers/apply_vtk_transform/CMakeLists.txt new file mode 100644 index 000000000..8916a4431 --- /dev/null +++ b/Anima/registration/resamplers/apply_vtk_transform/CMakeLists.txt @@ -0,0 +1,36 @@ +if(BUILD_TOOLS AND USE_VTK AND VTK_FOUND) + +project(animaApplyVtkTransform) + +## ############################################################################# +## 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} + ${VTK_PREFIX}IOLegacy + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/registration/resamplers/apply_vtk_transform/animaApplyVtkTransform.cxx b/Anima/registration/resamplers/apply_vtk_transform/animaApplyVtkTransform.cxx new file mode 100644 index 000000000..38967e2a6 --- /dev/null +++ b/Anima/registration/resamplers/apply_vtk_transform/animaApplyVtkTransform.cxx @@ -0,0 +1,180 @@ +#include +#include + + +#include +#include + +#include +#include +#include + + + +int main(int ac, const char** av) +{ + + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + // Scale + TCLAP::ValueArg + tArg("t", + "t_value", + "Factor of scaling for the z axis.", + false, + 1, + "double", + cmd + ); + + TCLAP::ValueArg + sArg("s", + "s_value", + "Factor of scaling for the s axis.", + false, + 1, + "double", + cmd + ); + TCLAP::ValueArg + rArg("r", + "r_value", + "Factor of scaling for the r axis.", + false, + 1, + "double", + cmd + ); + + // Rotation + TCLAP::ValueArg + wArg("w", + "w_value", + "Angle of the translation aroud the z axis.", + false, + 0, + "double", + cmd + ); + + TCLAP::ValueArg + vArg("v", + "v_value", + "Angle of the translation aroud the y axis", + false, + 0, + "double", + cmd + ); + TCLAP::ValueArg + uArg("u", + "u_value", + "Angle of the translation aroud the x axis", + false, + 0, + "double", + cmd + ); + + + // Translation + TCLAP::ValueArg + zArg("z", + "z_value", + "Z value of the translation.", + false, + 0, + "double", + cmd + ); + + TCLAP::ValueArg + yArg("y", + "y_value", + "Z value of the translation", + false, + 0, + "double", + cmd + ); + TCLAP::ValueArg + xArg("x", + "x_value", + "X value of the translation", + false, + 0, + "double", + cmd + ); + + + + // Files + TCLAP::ValueArg + outputArg("o", + "output", + "the registered moving object", + true, + "", + "Filename", + cmd + ); + TCLAP::ValueArg + inputArg("i", + "reference", + "The reference object", + true, + "", + "Filename", + cmd + ); + + try + { + cmd.parse(ac,av); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + vtkPolyDataReader *reader = vtkPolyDataReader::New(); + vtkPolyData *data = vtkPolyData::New(); + vtkPoints *points = vtkPoints::New(); + vtkPoints *newPoints = vtkPoints::New(); + + + // Read from input + reader->SetFileName(inputArg.getValue().c_str()); + reader->Update(); + data = reader->GetOutput(); + points = data->GetPoints(); + + vtkTransform *transform = vtkTransform::New(); + // Set translation + transform->Translate(xArg.getValue(), yArg.getValue(), zArg.getValue()); + + // Set Rotation + transform->RotateX(uArg.getValue()); + transform->RotateY(vArg.getValue()); + transform->RotateZ(wArg.getValue()); + + // Set Scale + transform->Scale(rArg.getValue(), sArg.getValue(), tArg.getValue()); + + // Apply to output + std::cout<<"transformation matrix : "; + transform->GetMatrix()->Print(std::cout); + transform->TransformPoints(points, newPoints); + data->SetPoints(newPoints); + + // Write to output + std::string outFileName = outputArg.getValue(); + vtkPolyDataWriter *writer = vtkPolyDataWriter::New(); + writer->SetFileName(outFileName.c_str()); + std::cout<< "write in : " << outFileName.c_str() <SetInputData(data); + writer->Update(); +} + From afa383d5374e44e3fb266a53573a5f4afc1da911 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 15:03:55 +0200 Subject: [PATCH 12/17] fusion of registration **part 2** --- Anima/registration/algorithms/animaMCMBlockMatcher.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/Anima/registration/algorithms/animaMCMBlockMatcher.hxx b/Anima/registration/algorithms/animaMCMBlockMatcher.hxx index a7d7d566c..1e6f69d33 100644 --- a/Anima/registration/algorithms/animaMCMBlockMatcher.hxx +++ b/Anima/registration/algorithms/animaMCMBlockMatcher.hxx @@ -39,6 +39,7 @@ MCMBlockMatcher { typename MCMInterpolatorType::Pointer interpolator = MCMInterpolatorType::New(); interpolator->SetReferenceOutputModel(this->GetReferenceImage()->GetDescriptionModel()); + interpolator->SetDDIInterpolationMethod(3); interpolator->Register(); return interpolator; From 02eea2abf9580506c403f48cd413dcb541270ef6 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 15:17:41 +0200 Subject: [PATCH 13/17] fusion of registration **part 3** --- Anima/registration/CMakeLists.txt | 1 + .../CMakeLists.txt | 44 +++++ .../animaDenseMCMSVFBMRegistration.cxx | 161 ++++++++++++++++++ ...nimaPyramidalDenseMCMSVFMatchingBridge.hxx | 1 + 4 files changed, 207 insertions(+) create mode 100644 Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt create mode 100644 Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx diff --git a/Anima/registration/CMakeLists.txt b/Anima/registration/CMakeLists.txt index 2b51fad79..3ce220fef 100644 --- a/Anima/registration/CMakeLists.txt +++ b/Anima/registration/CMakeLists.txt @@ -5,6 +5,7 @@ project(ANIMA-REGISTRATION) ################################################################################ add_subdirectory(anatomical-commands) +add_subdirectory(dense-mcm-svf-bm-registration) add_subdirectory(diffusion-commands) add_subdirectory(symmetry) add_subdirectory(resamplers) diff --git a/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt b/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt new file mode 100644 index 000000000..e7656964a --- /dev/null +++ b/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt @@ -0,0 +1,44 @@ +if(BUILD_TOOLS AND USE_RPI AND RPI_FOUND) + +project(animaDenseMCMSVFBMRegistration) + +## ############################################################################# +## 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} + ${ITK_TRANSFORM_LIBRARIES} + ITKOptimizers + ITKStatistics + AnimaMCMSimilarity + AnimaMCM + AnimaMCMBase + AnimaOptimizers + ${NLOPT_LIBRARY} + ) + +## ############################################################################# +## install +## ############################################################################# + +set_exe_install_rules(${PROJECT_NAME}) + +endif() diff --git a/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx b/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx new file mode 100644 index 000000000..d0790228c --- /dev/null +++ b/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +#include +#include + +int main(int ac, const char** av) +{ + const unsigned int Dimension = 3; + typedef anima::PyramidalDenseMCMSVFMatchingBridge PyramidBMType; + typedef PyramidBMType::InputImageType InputImageType; + typedef InputImageType::InternalPixelType InputInternalPixelType; + + // Parsing arguments + TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); + + // Setting up parameters + TCLAP::ValueArg fixedArg("r","refimage","Fixed image",true,"","fixed image",cmd); + TCLAP::ValueArg movingArg("m","movingimage","Moving image",true,"","moving image",cmd); + TCLAP::ValueArg outArg("o","outputimage","Output (registered) image",true,"","output image",cmd); + TCLAP::ValueArg outputTransformArg("O","outtransform","Output transformation",false,"","output transform",cmd); + TCLAP::SwitchArg ppdImageArg("P","ppd", "Re-orientation strategy set to preservation of principal direction (default: finite strain)", cmd, false); + + TCLAP::ValueArg blockSizeArg("","bs","Block size (default: 5)",false,5,"block size",cmd); + TCLAP::ValueArg blockSpacingArg("","sp","Block spacing (default: 2)",false,2,"block spacing",cmd); + TCLAP::ValueArg stdevThresholdArg("s","stdev","Threshold block standard deviation (default: 0.1)",false,0.1,"block minimal standard deviation",cmd); + TCLAP::ValueArg percentageKeptArg("k","per-kept","Percentage of blocks with the highest variance kept (default: 0.8)",false,0.8,"percentage of blocks kept",cmd); + + TCLAP::ValueArg blockTransfoArg("t","in-transform","Transformation computed between blocks (0: translation, 1: rigid, 2: affine, default: 0)",false,0,"transformation between blocks",cmd); + TCLAP::ValueArg blockMetricArg("","metric","Similarity metric between blocks (0: basic mean squares, 1: one to on basic mean squares, 2: MCM mean squares, 3: MCM correlation, default: 1)",false,1,"similarity metric",cmd); + TCLAP::ValueArg blockOrientationArg("","bor","Re-orientation strategy when matching blocks (0: none, 1: finite strain, 2: PPD, default: 2)",false,2,"block re-orientation",cmd); + TCLAP::ValueArg smallDeltaArg("", "small-delta", "Diffusion small delta (in seconds)", false, anima::DiffusionSmallDelta, "small delta", cmd); + TCLAP::ValueArg bigDeltaArg("", "big-delta", "Diffusion big delta (in seconds)", false, anima::DiffusionBigDelta, "big delta", cmd); + TCLAP::ValueArg bvalArg("b","bvals","B-values",false,"","b-values",cmd); + TCLAP::ValueArg bvecArg("v","bvec","Gradient direction",false,"","gradient directions",cmd); + + TCLAP::ValueArg optimizerArg("","opt","Optimizer for optimal block search (0: Exhaustive, 1: Bobyqa, default: 1)",false,1,"optimizer",cmd); + TCLAP::ValueArg maxIterationsArg("","mi","Maximum block match iterations (default: 10)",false,10,"maximum iterations",cmd); + TCLAP::ValueArg minErrorArg("","me","Minimal distance between consecutive estimated transforms (default: 0.01)",false,0.01,"minimal distance between transforms",cmd); + + TCLAP::ValueArg optimizerMaxIterationsArg("","oi","Maximum iterations for local optimizer (default: 100)",false,100,"maximum local optimizer iterations",cmd); + + TCLAP::ValueArg searchStepArg("","st","Search step for exhaustive search (default: 1)",false,1,"exhaustive optimizer search step",cmd); + + TCLAP::ValueArg translateUpperBoundArg("","tub","Upper bound on translation (in voxels, default: 3)",false,3,"Bobyqa translate upper bound",cmd); + TCLAP::ValueArg angleUpperBoundArg("","aub","Upper bound on angles for bobyqa (in degrees, default: 180)",false,180,"Bobyqa angle upper bound",cmd); + TCLAP::ValueArg scaleUpperBoundArg("","scu","Upper bound on scale for bobyqa (default: 3)",false,3,"Bobyqa scale upper bound",cmd); + + TCLAP::ValueArg symmetryArg("","sym-reg","Registration symmetry type (0: asymmetric, 1: symmetric, 2: kissing, default: 0)",false,0,"symmetry type",cmd); + TCLAP::ValueArg agregatorArg("a","agregator","Transformation agregator type (0: Baloo, 1: M-smoother, default: 0)",false,0,"agregator type",cmd); + TCLAP::ValueArg extrapolationSigmaArg("","fs","Sigma for extrapolation of local pairings (default: 3)",false,3,"extrapolation sigma",cmd); + TCLAP::ValueArg elasticSigmaArg("","es","Sigma for elastic regularization (default: 3)",false,3,"elastic regularization sigma",cmd); + TCLAP::ValueArg outlierSigmaArg("","os","Sigma for outlier rejection among local pairings (default: 3)",false,3,"outlier rejection sigma",cmd); + TCLAP::ValueArg mEstimateConvergenceThresholdArg("","met","Threshold to consider m-estimator converged (default: 0.01)",false,0.01,"m-estimation convergence threshold",cmd); + TCLAP::ValueArg bchOrderArg("B","bch-order","BCH composition order (default: 1)",false,1,"BCH order",cmd); + TCLAP::ValueArg expOrderArg("e","exp-order","Order of field exponentiation approximation (in between 0 and 1, default: 0)",false,0,"exponentiation order",cmd); + + TCLAP::ValueArg numPyramidLevelsArg("p","pyr","Number of pyramid levels (default: 3)",false,3,"number of pyramid levels",cmd); + TCLAP::ValueArg lastPyramidLevelArg("l","last-level","Index of the last pyramid level explored (default: 0)",false,0,"last pyramid level",cmd); + TCLAP::ValueArg numThreadsArg("T","threads","Number of execution threads (default: 0 = all cores)",false,0,"number of threads",cmd); + + try + { + cmd.parse(ac,av); + } + catch (TCLAP::ArgException& e) + { + std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; + return EXIT_FAILURE; + } + + PyramidBMType::Pointer matcher = PyramidBMType::New(); + + anima::MCMFileReader refReader; + refReader.SetFileName(fixedArg.getValue()); + refReader.Update(); + + matcher->SetReferenceImage(refReader.GetModelVectorImage()); + + anima::MCMFileReader floReader; + floReader.SetFileName(movingArg.getValue()); + floReader.Update(); + + matcher->SetFloatingImage(floReader.GetModelVectorImage()); + + if ((bvalArg.getValue() != "")&&(bvecArg.getValue() != "")) + { + typedef anima::GradientFileReader , double> GradientReaderType; + GradientReaderType gradientReader; + gradientReader.SetGradientFileName(bvecArg.getValue()); + gradientReader.SetBValueBaseString(bvalArg.getValue()); + gradientReader.SetSmallDelta(smallDeltaArg.getValue()); + gradientReader.SetBigDelta(bigDeltaArg.getValue()); + gradientReader.SetGradientIndependentNormalization(true); + + gradientReader.Update(); + + matcher->SetGradientStrengths(gradientReader.GetGradientStrengths()); + matcher->SetSmallDelta(smallDeltaArg.getValue()); + matcher->SetBigDelta(bigDeltaArg.getValue()); + matcher->SetGradientDirections(gradientReader.GetGradients()); + } + + // Setting matcher arguments + matcher->SetBlockSize( blockSizeArg.getValue() ); + matcher->SetBlockSpacing( blockSpacingArg.getValue() ); + matcher->SetStDevThreshold( stdevThresholdArg.getValue() ); + matcher->SetTransform( (Transform) blockTransfoArg.getValue() ); + matcher->SetMetric( (Metric) blockMetricArg.getValue() ); + matcher->SetMetricOrientation( (MetricOrientationType) blockOrientationArg.getValue() ); + matcher->SetFiniteStrainImageReorientation(!ppdImageArg.isSet()); + matcher->SetOptimizer( (Optimizer) optimizerArg.getValue() ); + matcher->SetMaximumIterations( maxIterationsArg.getValue() ); + matcher->SetMinimalTransformError( minErrorArg.getValue() ); + matcher->SetOptimizerMaximumIterations( optimizerMaxIterationsArg.getValue() ); + matcher->SetStepSize( searchStepArg.getValue() ); + matcher->SetTranslateUpperBound( translateUpperBoundArg.getValue() ); + matcher->SetAngleUpperBound( angleUpperBoundArg.getValue() ); + matcher->SetScaleUpperBound( scaleUpperBoundArg.getValue() ); + matcher->SetSymmetryType( (SymmetryType) symmetryArg.getValue() ); + matcher->SetAgregator( (Agregator) agregatorArg.getValue() ); + matcher->SetExtrapolationSigma(extrapolationSigmaArg.getValue()); + matcher->SetElasticSigma(elasticSigmaArg.getValue()); + matcher->SetOutlierSigma(outlierSigmaArg.getValue()); + matcher->SetMEstimateConvergenceThreshold(mEstimateConvergenceThresholdArg.getValue()); + matcher->SetBCHCompositionOrder(bchOrderArg.getValue()); + matcher->SetExponentiationOrder(expOrderArg.getValue()); + matcher->SetNumberOfPyramidLevels( numPyramidLevelsArg.getValue() ); + matcher->SetLastPyramidLevel( lastPyramidLevelArg.getValue() ); + + if (numThreadsArg.getValue() != 0) + matcher->SetNumberOfWorkUnits(numThreadsArg.getValue()); + + matcher->SetPercentageKept( percentageKeptArg.getValue() ); + + matcher->SetResultFile( outArg.getValue() ); + matcher->SetOutputTransformFile( outputTransformArg.getValue() ); + + itk::TimeProbe timer; + + timer.Start(); + + try + { + matcher->Update(); + matcher->WriteOutputs(); + } + catch (itk::ExceptionObject &e) + { + std::cerr << e << std::endl; + return EXIT_FAILURE; + } + + timer.Stop(); + + std::cout << "Elapsed Time: " << timer.GetTotal() << timer.GetUnit() << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Anima/registration/diffusion-commands/dense-mcm-svf-bm-registration/animaPyramidalDenseMCMSVFMatchingBridge.hxx b/Anima/registration/diffusion-commands/dense-mcm-svf-bm-registration/animaPyramidalDenseMCMSVFMatchingBridge.hxx index fe53eb931..b68149527 100644 --- a/Anima/registration/diffusion-commands/dense-mcm-svf-bm-registration/animaPyramidalDenseMCMSVFMatchingBridge.hxx +++ b/Anima/registration/diffusion-commands/dense-mcm-svf-bm-registration/animaPyramidalDenseMCMSVFMatchingBridge.hxx @@ -77,6 +77,7 @@ PyramidalDenseMCMSVFMatchingBridge { typename InterpolatorType::Pointer interpolator = InterpolatorType::New(); interpolator->SetReferenceOutputModel(image->GetDescriptionModel()); + interpolator->SetDDIInterpolationMethod(3); interpolator->Register(); return interpolator; From 2ccacea648fae468e5684d4b705d85fa01815244 Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 17:28:54 +0200 Subject: [PATCH 14/17] remove mcm duplication tool --- Anima/registration/CMakeLists.txt | 1 - .../CMakeLists.txt | 44 ----- .../animaDenseMCMSVFBMRegistration.cxx | 161 ------------------ 3 files changed, 206 deletions(-) delete mode 100644 Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt delete mode 100644 Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx diff --git a/Anima/registration/CMakeLists.txt b/Anima/registration/CMakeLists.txt index 3ce220fef..2b51fad79 100644 --- a/Anima/registration/CMakeLists.txt +++ b/Anima/registration/CMakeLists.txt @@ -5,7 +5,6 @@ project(ANIMA-REGISTRATION) ################################################################################ add_subdirectory(anatomical-commands) -add_subdirectory(dense-mcm-svf-bm-registration) add_subdirectory(diffusion-commands) add_subdirectory(symmetry) add_subdirectory(resamplers) diff --git a/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt b/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt deleted file mode 100644 index e7656964a..000000000 --- a/Anima/registration/dense-mcm-svf-bm-registration/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -if(BUILD_TOOLS AND USE_RPI AND RPI_FOUND) - -project(animaDenseMCMSVFBMRegistration) - -## ############################################################################# -## 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} - ${ITK_TRANSFORM_LIBRARIES} - ITKOptimizers - ITKStatistics - AnimaMCMSimilarity - AnimaMCM - AnimaMCMBase - AnimaOptimizers - ${NLOPT_LIBRARY} - ) - -## ############################################################################# -## install -## ############################################################################# - -set_exe_install_rules(${PROJECT_NAME}) - -endif() diff --git a/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx b/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx deleted file mode 100644 index d0790228c..000000000 --- a/Anima/registration/dense-mcm-svf-bm-registration/animaDenseMCMSVFBMRegistration.cxx +++ /dev/null @@ -1,161 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -int main(int ac, const char** av) -{ - const unsigned int Dimension = 3; - typedef anima::PyramidalDenseMCMSVFMatchingBridge PyramidBMType; - typedef PyramidBMType::InputImageType InputImageType; - typedef InputImageType::InternalPixelType InputInternalPixelType; - - // Parsing arguments - TCLAP::CmdLine cmd("INRIA / IRISA - VisAGeS/Empenn Team", ' ',ANIMA_VERSION); - - // Setting up parameters - TCLAP::ValueArg fixedArg("r","refimage","Fixed image",true,"","fixed image",cmd); - TCLAP::ValueArg movingArg("m","movingimage","Moving image",true,"","moving image",cmd); - TCLAP::ValueArg outArg("o","outputimage","Output (registered) image",true,"","output image",cmd); - TCLAP::ValueArg outputTransformArg("O","outtransform","Output transformation",false,"","output transform",cmd); - TCLAP::SwitchArg ppdImageArg("P","ppd", "Re-orientation strategy set to preservation of principal direction (default: finite strain)", cmd, false); - - TCLAP::ValueArg blockSizeArg("","bs","Block size (default: 5)",false,5,"block size",cmd); - TCLAP::ValueArg blockSpacingArg("","sp","Block spacing (default: 2)",false,2,"block spacing",cmd); - TCLAP::ValueArg stdevThresholdArg("s","stdev","Threshold block standard deviation (default: 0.1)",false,0.1,"block minimal standard deviation",cmd); - TCLAP::ValueArg percentageKeptArg("k","per-kept","Percentage of blocks with the highest variance kept (default: 0.8)",false,0.8,"percentage of blocks kept",cmd); - - TCLAP::ValueArg blockTransfoArg("t","in-transform","Transformation computed between blocks (0: translation, 1: rigid, 2: affine, default: 0)",false,0,"transformation between blocks",cmd); - TCLAP::ValueArg blockMetricArg("","metric","Similarity metric between blocks (0: basic mean squares, 1: one to on basic mean squares, 2: MCM mean squares, 3: MCM correlation, default: 1)",false,1,"similarity metric",cmd); - TCLAP::ValueArg blockOrientationArg("","bor","Re-orientation strategy when matching blocks (0: none, 1: finite strain, 2: PPD, default: 2)",false,2,"block re-orientation",cmd); - TCLAP::ValueArg smallDeltaArg("", "small-delta", "Diffusion small delta (in seconds)", false, anima::DiffusionSmallDelta, "small delta", cmd); - TCLAP::ValueArg bigDeltaArg("", "big-delta", "Diffusion big delta (in seconds)", false, anima::DiffusionBigDelta, "big delta", cmd); - TCLAP::ValueArg bvalArg("b","bvals","B-values",false,"","b-values",cmd); - TCLAP::ValueArg bvecArg("v","bvec","Gradient direction",false,"","gradient directions",cmd); - - TCLAP::ValueArg optimizerArg("","opt","Optimizer for optimal block search (0: Exhaustive, 1: Bobyqa, default: 1)",false,1,"optimizer",cmd); - TCLAP::ValueArg maxIterationsArg("","mi","Maximum block match iterations (default: 10)",false,10,"maximum iterations",cmd); - TCLAP::ValueArg minErrorArg("","me","Minimal distance between consecutive estimated transforms (default: 0.01)",false,0.01,"minimal distance between transforms",cmd); - - TCLAP::ValueArg optimizerMaxIterationsArg("","oi","Maximum iterations for local optimizer (default: 100)",false,100,"maximum local optimizer iterations",cmd); - - TCLAP::ValueArg searchStepArg("","st","Search step for exhaustive search (default: 1)",false,1,"exhaustive optimizer search step",cmd); - - TCLAP::ValueArg translateUpperBoundArg("","tub","Upper bound on translation (in voxels, default: 3)",false,3,"Bobyqa translate upper bound",cmd); - TCLAP::ValueArg angleUpperBoundArg("","aub","Upper bound on angles for bobyqa (in degrees, default: 180)",false,180,"Bobyqa angle upper bound",cmd); - TCLAP::ValueArg scaleUpperBoundArg("","scu","Upper bound on scale for bobyqa (default: 3)",false,3,"Bobyqa scale upper bound",cmd); - - TCLAP::ValueArg symmetryArg("","sym-reg","Registration symmetry type (0: asymmetric, 1: symmetric, 2: kissing, default: 0)",false,0,"symmetry type",cmd); - TCLAP::ValueArg agregatorArg("a","agregator","Transformation agregator type (0: Baloo, 1: M-smoother, default: 0)",false,0,"agregator type",cmd); - TCLAP::ValueArg extrapolationSigmaArg("","fs","Sigma for extrapolation of local pairings (default: 3)",false,3,"extrapolation sigma",cmd); - TCLAP::ValueArg elasticSigmaArg("","es","Sigma for elastic regularization (default: 3)",false,3,"elastic regularization sigma",cmd); - TCLAP::ValueArg outlierSigmaArg("","os","Sigma for outlier rejection among local pairings (default: 3)",false,3,"outlier rejection sigma",cmd); - TCLAP::ValueArg mEstimateConvergenceThresholdArg("","met","Threshold to consider m-estimator converged (default: 0.01)",false,0.01,"m-estimation convergence threshold",cmd); - TCLAP::ValueArg bchOrderArg("B","bch-order","BCH composition order (default: 1)",false,1,"BCH order",cmd); - TCLAP::ValueArg expOrderArg("e","exp-order","Order of field exponentiation approximation (in between 0 and 1, default: 0)",false,0,"exponentiation order",cmd); - - TCLAP::ValueArg numPyramidLevelsArg("p","pyr","Number of pyramid levels (default: 3)",false,3,"number of pyramid levels",cmd); - TCLAP::ValueArg lastPyramidLevelArg("l","last-level","Index of the last pyramid level explored (default: 0)",false,0,"last pyramid level",cmd); - TCLAP::ValueArg numThreadsArg("T","threads","Number of execution threads (default: 0 = all cores)",false,0,"number of threads",cmd); - - try - { - cmd.parse(ac,av); - } - catch (TCLAP::ArgException& e) - { - std::cerr << "Error: " << e.error() << "for argument " << e.argId() << std::endl; - return EXIT_FAILURE; - } - - PyramidBMType::Pointer matcher = PyramidBMType::New(); - - anima::MCMFileReader refReader; - refReader.SetFileName(fixedArg.getValue()); - refReader.Update(); - - matcher->SetReferenceImage(refReader.GetModelVectorImage()); - - anima::MCMFileReader floReader; - floReader.SetFileName(movingArg.getValue()); - floReader.Update(); - - matcher->SetFloatingImage(floReader.GetModelVectorImage()); - - if ((bvalArg.getValue() != "")&&(bvecArg.getValue() != "")) - { - typedef anima::GradientFileReader , double> GradientReaderType; - GradientReaderType gradientReader; - gradientReader.SetGradientFileName(bvecArg.getValue()); - gradientReader.SetBValueBaseString(bvalArg.getValue()); - gradientReader.SetSmallDelta(smallDeltaArg.getValue()); - gradientReader.SetBigDelta(bigDeltaArg.getValue()); - gradientReader.SetGradientIndependentNormalization(true); - - gradientReader.Update(); - - matcher->SetGradientStrengths(gradientReader.GetGradientStrengths()); - matcher->SetSmallDelta(smallDeltaArg.getValue()); - matcher->SetBigDelta(bigDeltaArg.getValue()); - matcher->SetGradientDirections(gradientReader.GetGradients()); - } - - // Setting matcher arguments - matcher->SetBlockSize( blockSizeArg.getValue() ); - matcher->SetBlockSpacing( blockSpacingArg.getValue() ); - matcher->SetStDevThreshold( stdevThresholdArg.getValue() ); - matcher->SetTransform( (Transform) blockTransfoArg.getValue() ); - matcher->SetMetric( (Metric) blockMetricArg.getValue() ); - matcher->SetMetricOrientation( (MetricOrientationType) blockOrientationArg.getValue() ); - matcher->SetFiniteStrainImageReorientation(!ppdImageArg.isSet()); - matcher->SetOptimizer( (Optimizer) optimizerArg.getValue() ); - matcher->SetMaximumIterations( maxIterationsArg.getValue() ); - matcher->SetMinimalTransformError( minErrorArg.getValue() ); - matcher->SetOptimizerMaximumIterations( optimizerMaxIterationsArg.getValue() ); - matcher->SetStepSize( searchStepArg.getValue() ); - matcher->SetTranslateUpperBound( translateUpperBoundArg.getValue() ); - matcher->SetAngleUpperBound( angleUpperBoundArg.getValue() ); - matcher->SetScaleUpperBound( scaleUpperBoundArg.getValue() ); - matcher->SetSymmetryType( (SymmetryType) symmetryArg.getValue() ); - matcher->SetAgregator( (Agregator) agregatorArg.getValue() ); - matcher->SetExtrapolationSigma(extrapolationSigmaArg.getValue()); - matcher->SetElasticSigma(elasticSigmaArg.getValue()); - matcher->SetOutlierSigma(outlierSigmaArg.getValue()); - matcher->SetMEstimateConvergenceThreshold(mEstimateConvergenceThresholdArg.getValue()); - matcher->SetBCHCompositionOrder(bchOrderArg.getValue()); - matcher->SetExponentiationOrder(expOrderArg.getValue()); - matcher->SetNumberOfPyramidLevels( numPyramidLevelsArg.getValue() ); - matcher->SetLastPyramidLevel( lastPyramidLevelArg.getValue() ); - - if (numThreadsArg.getValue() != 0) - matcher->SetNumberOfWorkUnits(numThreadsArg.getValue()); - - matcher->SetPercentageKept( percentageKeptArg.getValue() ); - - matcher->SetResultFile( outArg.getValue() ); - matcher->SetOutputTransformFile( outputTransformArg.getValue() ); - - itk::TimeProbe timer; - - timer.Start(); - - try - { - matcher->Update(); - matcher->WriteOutputs(); - } - catch (itk::ExceptionObject &e) - { - std::cerr << e << std::endl; - return EXIT_FAILURE; - } - - timer.Stop(); - - std::cout << "Elapsed Time: " << timer.GetTotal() << timer.GetUnit() << std::endl; - - return EXIT_SUCCESS; -} From bef94bc5591a74ca3f458279acee0139e99e99cf Mon Sep 17 00:00:00 2001 From: FLORENT LERAY Date: Wed, 12 Apr 2023 17:46:35 +0200 Subject: [PATCH 15/17] forgotten Setter SetDDIInterpolationMethod --- .../interpolator/animaMCMLinearInterpolateImageFunction.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h index e3c793e86..469e707e7 100644 --- a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h +++ b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h @@ -108,6 +108,8 @@ public itk::InterpolateImageFunction unsigned int GetFreeWorkIndex() const; void UnlockWorkIndex(unsigned int index) const; + + itkSetMacro(DDIInterpolationMethod, unsigned int) private: MCMLinearInterpolateImageFunction(const Self&); //purposely not implemented From 1c1ea37ba34142bcad28aece86f80e4518437646 Mon Sep 17 00:00:00 2001 From: Aymeric Stamm Date: Wed, 12 Apr 2023 17:47:56 +0200 Subject: [PATCH 16/17] Change as_matrix to inverse for vnl_matrix_inverse --- Anima/math-tools/data_io/animaTRKWriter.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Anima/math-tools/data_io/animaTRKWriter.cxx b/Anima/math-tools/data_io/animaTRKWriter.cxx index a4161248e..06222212b 100644 --- a/Anima/math-tools/data_io/animaTRKWriter.cxx +++ b/Anima/math-tools/data_io/animaTRKWriter.cxx @@ -151,7 +151,7 @@ void TRKWriter::Update() directionMatrix(i,j) = headerStr.vox_to_ras[i][j]; } - directionMatrix = vnl_matrix_inverse (directionMatrix).as_matrix(); + directionMatrix = vnl_matrix_inverse(directionMatrix).inverse(); for (unsigned int i = 0;i < headerStr.n_count;++i) { From 363e5d377e2f1bb3cb946736853d4a64391a6413 Mon Sep 17 00:00:00 2001 From: Aymeric Stamm Date: Wed, 12 Apr 2023 17:50:50 +0200 Subject: [PATCH 17/17] Move DDIInterpolationMethod setter to public methods in interpolator. --- .../interpolator/animaMCMLinearInterpolateImageFunction.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h index 469e707e7..cb17a8a71 100644 --- a/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h +++ b/Anima/math-tools/interpolator/animaMCMLinearInterpolateImageFunction.h @@ -72,6 +72,8 @@ public itk::InterpolateImageFunction return SizeType::Filled(1); } + itkSetMacro(DDIInterpolationMethod, unsigned int) + protected: MCMLinearInterpolateImageFunction(); virtual ~MCMLinearInterpolateImageFunction() {} @@ -108,8 +110,6 @@ public itk::InterpolateImageFunction unsigned int GetFreeWorkIndex() const; void UnlockWorkIndex(unsigned int index) const; - - itkSetMacro(DDIInterpolationMethod, unsigned int) private: MCMLinearInterpolateImageFunction(const Self&); //purposely not implemented