├── .gitignore ├── CMakeLists.txt ├── DWModeling ├── CMakeLists.txt ├── DWModeling.cxx ├── DWModeling.xml ├── Data │ ├── Baseline │ │ ├── MRModelingTest.nhdr.md5 │ │ └── MRModelingTest.raw.md5 │ ├── Input │ │ ├── CTHeadAxial.nhdr.md5 │ │ └── CTHeadAxial.raw.gz.md5 │ └── SampledPhantoms │ │ ├── 00943_SER18 │ │ ├── Input │ │ │ ├── 00943_SER18.nrrd │ │ │ └── Fitted volume frame 5-label.nrrd │ │ ├── README │ │ └── probing.nrrd │ │ └── README └── Testing │ ├── CMakeLists.txt │ └── Cxx │ ├── CMakeLists.txt │ └── MRModelingTest.cxx ├── DistanceMapBasedRegistration ├── CMakeLists.txt ├── DistanceMapBasedRegistration.py ├── Resources │ └── Icons │ │ └── DistanceMapBasedRegistration.png └── Testing │ ├── CMakeLists.txt │ └── Python │ └── CMakeLists.txt ├── License.txt ├── Logo └── SlicerProstate.ai ├── QuadEdgeSurfaceMesher ├── CMakeLists.txt ├── QuadEdgeSurfaceMesher.cxx └── QuadEdgeSurfaceMesher.xml ├── README.md ├── SegmentationSmoothing ├── CMakeLists.txt ├── SegmentationSmoothing.cxx ├── SegmentationSmoothing.xml └── Testing │ ├── CMakeLists.txt │ └── Cxx │ ├── CMakeLists.txt │ └── SegmentationSmoothingTest.cxx └── SlicerProstate.png /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *pyc 3 | *swp 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.3...3.19.7 FATAL_ERROR) 2 | 3 | project(SlicerProstate) 4 | 5 | #----------------------------------------------------------------------------- 6 | # Extension meta-information 7 | set(EXTENSION_HOMEPAGE "https://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Extensions/SlicerProstate") 8 | set(EXTENSION_CATEGORY "Informatics") 9 | set(EXTENSION_CONTRIBUTORS "Andrey Fedorov (Brigham and Women's Hospital), Andras Lasso (Queen's University), Alireza Mehrtash (Brigham and Women's Hospital)") 10 | set(EXTENSION_DESCRIPTION "SlicerProstate extension hosts various modules to facilitate processing and management of prostate image data, utilizing prostate images in image-guided interventions and development of the imaging biomarkers of the prostate cancer.") 11 | set(EXTENSION_ICONURL "https://wiki.slicer.org/slicerWiki/images/8/87/SlicerProstate_Logo_1.0_128x128.png") 12 | set(EXTENSION_SCREENSHOTURLS "https://wiki.slicer.org/slicerWiki/images/f/ff/DistanceMapBasedRegistration_example1.png https://wiki.slicer.org/slicerWiki/images/3/36/SegmentationSmoothing_example1.png https://wiki.slicer.org/slicerWiki/images/c/c3/QuadEdgeSurfaceMesher_example1.png") 13 | 14 | #----------------------------------------------------------------------------- 15 | # Extension dependencies 16 | find_package(Slicer REQUIRED) 17 | include(${Slicer_USE_FILE}) 18 | 19 | #----------------------------------------------------------------------------- 20 | # Extension modules 21 | add_subdirectory(QuadEdgeSurfaceMesher) 22 | add_subdirectory(SegmentationSmoothing) 23 | add_subdirectory(DistanceMapBasedRegistration) 24 | add_subdirectory(DWModeling) 25 | ## NEXT_MODULE 26 | 27 | #----------------------------------------------------------------------------- 28 | include(${Slicer_EXTENSION_GENERATE_CONFIG}) 29 | include(${Slicer_EXTENSION_CPACK}) 30 | -------------------------------------------------------------------------------- /DWModeling/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | set(MODULE_NAME DWModeling) 3 | 4 | #----------------------------------------------------------------------------- 5 | set(MODULE_INCLUDE_DIRECTORIES 6 | ) 7 | 8 | set(MODULE_SRCS 9 | ) 10 | 11 | set(MODULE_TARGET_LIBRARIES 12 | ${ITK_LIBRARIES} 13 | ) 14 | 15 | #----------------------------------------------------------------------------- 16 | SEMMacroBuildCLI( 17 | NAME ${MODULE_NAME} 18 | TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} 19 | INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} 20 | ADDITIONAL_SRCS ${MODULE_SRCS} 21 | #EXECUTABLE_ONLY 22 | ) 23 | 24 | #----------------------------------------------------------------------------- 25 | if(BUILD_TESTING) 26 | # add_subdirectory(Testing) 27 | endif() 28 | -------------------------------------------------------------------------------- /DWModeling/DWModeling.cxx: -------------------------------------------------------------------------------- 1 | #include "itkImageFileWriter.h" 2 | #include "itkImageDuplicator.h" 3 | #include "itkMetaDataObject.h" 4 | #include "itkLevenbergMarquardtOptimizer.h" 5 | #include "itkArray.h" 6 | 7 | 8 | #include "itkPluginUtilities.h" 9 | //#include "lmcurve.h" 10 | 11 | #include "DWModelingCLP.h" 12 | #include "itkImageRegionIteratorWithIndex.h" 13 | #include "itkImageRegionConstIteratorWithIndex.h" 14 | 15 | //double f(double t, const double *p){ 16 | // return p[1]*exp(p[0]*t); 17 | //} 18 | 19 | //int fit_exp(double *par, int m_dat, double *t, double *y); 20 | 21 | #define SimpleAttributeGetMethodMacro(name, key, type) \ 22 | type Get##name(itk::MetaDataDictionary& dictionary) \ 23 | {\ 24 | type value = type(); \ 25 | if (dictionary.HasKey(key))\ 26 | {\ 27 | /* attributes stored as strings */ \ 28 | std::string valueString; \ 29 | itk::ExposeMetaData(dictionary, key, valueString); \ 30 | std::stringstream valueStream(valueString); \ 31 | valueStream >> value; \ 32 | }\ 33 | else\ 34 | {\ 35 | itkGenericExceptionMacro("Missing attribute '" key "'.");\ 36 | }\ 37 | return value;\ 38 | } 39 | 40 | //SimpleAttributeGetMethodMacro(EchoTime, "MultiVolume.DICOM.EchoTime", float); 41 | SimpleAttributeGetMethodMacro(RepetitionTime, "MultiVolume.DICOM.RepetitionTime",float); 42 | SimpleAttributeGetMethodMacro(FlipAngle, "MultiVolume.DICOM.FlipAngle", float); 43 | 44 | std::vector GetBvalues(itk::MetaDataDictionary& dictionary) 45 | { 46 | std::vector bValues; 47 | 48 | if (dictionary.HasKey("MultiVolume.FrameIdentifyingDICOMTagName")) 49 | { 50 | std::string tag; 51 | itk::ExposeMetaData(dictionary, "MultiVolume.FrameIdentifyingDICOMTagName", tag); 52 | if (dictionary.HasKey("MultiVolume.FrameLabels")) 53 | { 54 | // Acquisition parameters stored as text, FrameLabels are comma separated 55 | std::string frameLabelsString; 56 | itk::ExposeMetaData(dictionary, "MultiVolume.FrameLabels", frameLabelsString); 57 | std::stringstream frameLabelsStream(frameLabelsString); 58 | if (tag.find("B-value") != std::string::npos) 59 | { 60 | float t; 61 | while (frameLabelsStream >> t) 62 | { 63 | bValues.push_back(t); 64 | frameLabelsStream.ignore(1); // skip the comma 65 | } 66 | } 67 | else 68 | { 69 | itkGenericExceptionMacro("Unrecognized frame identifying DICOM tag name " << tag); 70 | } 71 | } 72 | else 73 | { 74 | itkGenericExceptionMacro("Missing attribute 'MultiVolume.FrameLabels'.") 75 | } 76 | } 77 | else 78 | { 79 | itkGenericExceptionMacro("Missing attribute 'MultiVolume.FrameIdentifyingDICOMTagName'."); 80 | } 81 | 82 | return bValues; 83 | } 84 | 85 | class DecayCostFunction: public itk::MultipleValuedCostFunction 86 | { 87 | public: 88 | typedef DecayCostFunction Self; 89 | typedef itk::MultipleValuedCostFunction Superclass; 90 | typedef itk::SmartPointer Pointer; 91 | typedef itk::SmartPointer ConstPointer; 92 | itkNewMacro( Self ); 93 | 94 | typedef Superclass::ParametersType ParametersType; 95 | typedef Superclass::DerivativeType DerivativeType; 96 | typedef Superclass::MeasureType MeasureType, ArrayType; 97 | typedef Superclass::ParametersValueType ValueType; 98 | 99 | enum Model { 100 | MonoExponential = 0, 101 | BiExponential = 1, 102 | Kurtosis = 2, 103 | StretchedExponential = 3, 104 | Gamma = 4 105 | }; 106 | 107 | enum { SpaceDimension = 3 }; 108 | 109 | DecayCostFunction() 110 | { 111 | modelType = BiExponential; 112 | } 113 | 114 | void SetModelType(Model mt){ 115 | modelType = mt; 116 | switch(modelType){ 117 | case BiExponential: 118 | // initialize initial parameters 119 | initialValue = ParametersType(4); 120 | initialValue[0] = 0; // set to b0! 121 | initialValue[1] = 0.7; 122 | initialValue[2] = 0.00025; 123 | initialValue[3] = 0.002; 124 | 125 | // initialize parameter meaning (store this in NRRD? save units?) 126 | parametersMeaning.clear(); 127 | parametersMeaning.push_back("Scale"); 128 | parametersMeaning.push_back("Fast diffusion (perfusion) fraction"); 129 | parametersMeaning.push_back("Slow diffusion coefficient"); 130 | parametersMeaning.push_back("Fast diffusion (perfusion) coefficient"); 131 | 132 | break; 133 | case Kurtosis: 134 | // initialize initial parameters 135 | initialValue = ParametersType(3); 136 | initialValue[0] = 0; // set to b0! 137 | initialValue[1] = 1; 138 | initialValue[2] = 0.0015; 139 | 140 | // initialize parameter meaning (store this in NRRD? save units?) 141 | parametersMeaning.clear(); 142 | parametersMeaning.push_back("Scale"); 143 | parametersMeaning.push_back("Kurtosis"); 144 | parametersMeaning.push_back("Kurtosis diffusion"); 145 | 146 | break; 147 | case MonoExponential: 148 | initialValue = ParametersType(2); 149 | initialValue[0] = 0; 150 | initialValue[1] = 0.0015; 151 | 152 | // initialize parameter meaning (store this in NRRD? save units?) 153 | parametersMeaning.clear(); 154 | parametersMeaning.push_back("Scale"); 155 | parametersMeaning.push_back("ADC"); 156 | 157 | break; 158 | case StretchedExponential: 159 | initialValue = ParametersType(3); 160 | initialValue[0] = 0; 161 | initialValue[1] = 0.0017; 162 | initialValue[2] = 0.7; 163 | 164 | parametersMeaning.clear(); 165 | // See Bennett et al. 2003 166 | // Bennett KM, Schmainda KM, Bennett RT, Rowe DB, Lu H, Hyde JS. 167 | // Characterization of continuously distributed cortical water diffusion 168 | // rates with a stretched-exponential model. 169 | // Magn Reson Med. 2003;50: 727–734. doi:10.1002/mrm.10581 170 | parametersMeaning.push_back("Scale"); 171 | // the quantity derived from fitting the stretched-exponential 172 | // function to the data 173 | parametersMeaning.push_back("Distributed Diffusion Coefficient (DDC)"); 174 | // Stretching parameter between 0 and 1 characterizing deviation of the 175 | // signal attennuation from the monoexponential behavior 176 | parametersMeaning.push_back("Alpha"); 177 | 178 | break; 179 | 180 | case Gamma: 181 | initialValue = ParametersType(3); 182 | initialValue[0] = 0; 183 | initialValue[1] = 1.5; 184 | initialValue[2] = 0.002; 185 | 186 | parametersMeaning.clear(); 187 | // See Oshio et al. 2014 188 | // Oshio K, Shinmoto H, Mulkern RV. Interpretation of diffusion MR 189 | // imaging data using a gamma distribution model. 190 | // Magn Reson Med Sci. 2014;13: 191–195. doi:10.2463/mrms.2014-0016 191 | parametersMeaning.push_back("Scale"); 192 | parametersMeaning.push_back("k parameter of the gamma distribution"); 193 | parametersMeaning.push_back("theta parameter of the gamma distribution"); 194 | 195 | break; 196 | 197 | default: 198 | abort(); // not implemented! 199 | } 200 | } 201 | 202 | ParametersType GetInitialValue(){ 203 | return initialValue; 204 | } 205 | 206 | void SetInitialValues(ParametersType initialParameters){ 207 | // TODO: add model-specific checks 208 | if(initialParameters.size() != initialValue.size()) 209 | return; 210 | for(int i=0;i parametersMeaning; 466 | }; 467 | 468 | // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm 469 | void OnlineVariance(itk::MultipleValuedCostFunction::MeasureType &values, 470 | double &mean, double &SD){ 471 | double n = 0, M2 = 0; 472 | 473 | for(unsigned int i=0;i VectorVolumeType; 487 | typedef VectorVolumeType::RegionType VectorVolumeRegionType; 488 | typedef itk::ImageFileReader VectorVolumeReaderType; 489 | 490 | typedef unsigned char MaskVolumePixelType; 491 | typedef float MapVolumePixelType; 492 | typedef itk::Image MaskVolumeType; 493 | typedef itk::Image MapVolumeType; 494 | typedef itk::ImageFileReader MaskVolumeReaderType; 495 | typedef itk::ImageFileWriter MapWriterType; 496 | typedef itk::ImageFileWriter FittedVolumeWriterType; 497 | 498 | typedef itk::Image OutputVolumeType; 499 | typedef itk::ImageDuplicator DuplicatorType; 500 | typedef itk::ImageFileWriter< MapVolumeType> MapVolumeWriterType; 501 | 502 | typedef itk::ImageRegionConstIterator InputVectorVolumeIteratorType; 503 | typedef itk::ImageRegionIterator OutputVectorVolumeIteratorType; 504 | typedef itk::ImageRegionConstIterator MaskVolumeIteratorType; 505 | typedef itk::ImageRegionIterator MapVolumeIteratorType; 506 | 507 | void SaveMap(MapVolumeType::Pointer map, std::string fileName){ 508 | MapWriterType::Pointer writer = MapWriterType::New(); 509 | writer->SetInput(map); 510 | writer->SetFileName(fileName.c_str()); 511 | writer->SetUseCompression(1); 512 | writer->Update(); 513 | } 514 | 515 | // Use an anonymous namespace to keep class types and function names 516 | // from colliding when module is used as shared object module. Every 517 | // thing should be in an anonymous namespace except for the module 518 | // entry point, e.g. main() 519 | // 520 | int main( int argc, char * argv[]) 521 | { 522 | PARSE_ARGS; 523 | 524 | if((bValuesToInclude.size()>0) && (bValuesToExclude.size()>0)){ 525 | std::cerr << "ERROR: Either inclusion or exclusion b-values list can be specified, not both!" << std::endl; 526 | return -1; 527 | } 528 | 529 | //Read VectorVolume 530 | VectorVolumeReaderType::Pointer multiVolumeReader 531 | = VectorVolumeReaderType::New(); 532 | multiVolumeReader->SetFileName(imageName.c_str() ); 533 | multiVolumeReader->Update(); 534 | VectorVolumeType::Pointer inputVectorVolume = multiVolumeReader->GetOutput(); 535 | 536 | // Read mask 537 | MaskVolumeType::Pointer maskVolume; 538 | if(maskName != ""){ 539 | MaskVolumeReaderType::Pointer maskReader = MaskVolumeReaderType::New(); 540 | maskReader->SetFileName(maskName.c_str()); 541 | maskReader->Update(); 542 | maskVolume = maskReader->GetOutput(); 543 | } else { 544 | maskVolume = MaskVolumeType::New(); 545 | maskVolume->SetRegions(inputVectorVolume->GetLargestPossibleRegion()); 546 | maskVolume->CopyInformation(inputVectorVolume); 547 | maskVolume->Allocate(); 548 | maskVolume->FillBuffer(1); 549 | } 550 | 551 | //Look for tags representing the acquisition parameters 552 | // 553 | // 554 | 555 | // Trigger times 556 | std::vector bValues; 557 | // list of b-values to be passed to the optimizer 558 | float *bValuesPtr, *imageValuesPtr, *fittedValuesPtr; 559 | // "true" for the b-value and measurement pair to be used in fitting 560 | bool *bValuesMask; 561 | int bValuesTotal, bValuesSelected; 562 | try { 563 | 564 | bValues = GetBvalues(inputVectorVolume->GetMetaDataDictionary()); 565 | bValuesTotal = bValues.size(); 566 | bValuesMask = new bool[bValuesTotal]; 567 | 568 | // if the inclusion list is non-empty, use only the values requested by the 569 | // user 570 | if(bValuesToInclude.size()){ 571 | memset(bValuesMask,false,sizeof(bool)*bValuesTotal); 572 | bValuesSelected = 0; 573 | for(int i=0;i parameterMapVector; 625 | std::vector parameterMapItVector; 626 | 627 | if(modelName == "BiExponential") 628 | modelType = DecayCostFunction::BiExponential; 629 | else if(modelName == "MonoExponential") 630 | modelType = DecayCostFunction::MonoExponential; 631 | else if(modelName == "Kurtosis") 632 | modelType = DecayCostFunction::Kurtosis; 633 | else if(modelName == "StretchedExponential") 634 | modelType = DecayCostFunction::StretchedExponential; 635 | else if(modelName == "Gamma") 636 | modelType = DecayCostFunction::Gamma; 637 | else { 638 | std::cerr << "ERROR: Unknown model type specified!" << std::endl; 639 | return -1; 640 | } 641 | 642 | DecayCostFunction::Pointer costFunction = DecayCostFunction::New(); 643 | costFunction->SetModelType(modelType); 644 | 645 | // set initial parameters model-dependent 646 | DecayCostFunction::ParametersType initialValue = costFunction->GetInitialValue(); 647 | if(modelName == "BiExponential"){ 648 | initialValue[0] = biExpInitParameters[0]; 649 | initialValue[1] = biExpInitParameters[1]; 650 | initialValue[2] = biExpInitParameters[2]; 651 | initialValue[3] = biExpInitParameters[3]; 652 | } else if(modelName == "MonoExponential") { 653 | initialValue[0] = monoExpInitParameters[0]; 654 | initialValue[1] = monoExpInitParameters[1]; 655 | } else if(modelName == "Kurtosis") { 656 | initialValue[0] = kurtosisInitParameters[0]; 657 | initialValue[1] = kurtosisInitParameters[1]; 658 | initialValue[2] = kurtosisInitParameters[2]; 659 | } else if(modelName == "StretchedExponential") { 660 | initialValue[0] = stretchedExpInitParameters[0]; 661 | initialValue[1] = stretchedExpInitParameters[1]; 662 | initialValue[2] = stretchedExpInitParameters[2]; 663 | } else if(modelName == "Gamma") { 664 | initialValue[0] = gammaInitParameters[0]; 665 | initialValue[1] = gammaInitParameters[1]; 666 | initialValue[2] = gammaInitParameters[2]; 667 | } 668 | 669 | costFunction->SetInitialValues(initialValue); 670 | 671 | unsigned numberOfMaps; 672 | if(modelName == "Gamma") { 673 | // include computed mode map as output 674 | numberOfMaps = costFunction->GetNumberOfParameters()+1; 675 | } else { 676 | numberOfMaps = costFunction->GetNumberOfParameters(); 677 | } 678 | parameterMapVector.resize(numberOfMaps); 679 | 680 | for(int i=0;iSetRegions(maskVolume->GetLargestPossibleRegion()); 683 | parameterMapVector[i]->Allocate(); 684 | parameterMapVector[i]->FillBuffer(0); 685 | // note mask is initialized even if not passed by the user 686 | parameterMapVector[i]->CopyInformation(maskVolume); 687 | parameterMapVector[i]->FillBuffer(0); 688 | 689 | parameterMapItVector.push_back( 690 | MapVolumeIteratorType(parameterMapVector[i],parameterMapVector[i]->GetLargestPossibleRegion())); 691 | parameterMapItVector[i].GoToBegin(); 692 | } 693 | 694 | // Fitted values and error measure volumes are calculated independently of the model 695 | MapVolumeType::Pointer rsqrMap = MapVolumeType::New(); 696 | rsqrMap->SetRegions(maskVolume->GetLargestPossibleRegion()); 697 | rsqrMap->Allocate(); 698 | rsqrMap->FillBuffer(0); 699 | rsqrMap->CopyInformation(maskVolume); 700 | rsqrMap->FillBuffer(0); 701 | 702 | MapVolumeType::Pointer ssdFittedMap = MapVolumeType::New(); 703 | ssdFittedMap->SetRegions(maskVolume->GetLargestPossibleRegion()); 704 | ssdFittedMap->Allocate(); 705 | ssdFittedMap->FillBuffer(0); 706 | ssdFittedMap->CopyInformation(maskVolume); 707 | ssdFittedMap->FillBuffer(0); 708 | 709 | MapVolumeType::Pointer ssdMap = MapVolumeType::New(); 710 | ssdMap->SetRegions(maskVolume->GetLargestPossibleRegion()); 711 | ssdMap->Allocate(); 712 | ssdMap->FillBuffer(0); 713 | ssdMap->CopyInformation(maskVolume); 714 | ssdMap->FillBuffer(0); 715 | 716 | MapVolumeType::Pointer csFittedMap = MapVolumeType::New(); 717 | csFittedMap->SetRegions(maskVolume->GetLargestPossibleRegion()); 718 | csFittedMap->Allocate(); 719 | csFittedMap->FillBuffer(0); 720 | csFittedMap->CopyInformation(maskVolume); 721 | csFittedMap->FillBuffer(0); 722 | 723 | MapVolumeType::Pointer csMap = MapVolumeType::New(); 724 | csMap->SetRegions(maskVolume->GetLargestPossibleRegion()); 725 | csMap->Allocate(); 726 | csMap->FillBuffer(0); 727 | csMap->CopyInformation(maskVolume); 728 | csMap->FillBuffer(0); 729 | 730 | DuplicatorType::Pointer dup = DuplicatorType::New(); 731 | dup->SetInputImage(inputVectorVolume); 732 | dup->Update(); 733 | VectorVolumeType::Pointer fittedVolume = dup->GetOutput(); 734 | VectorVolumeType::PixelType zero = VectorVolumeType::PixelType(bValues.size()); 735 | for(int i=0;iFillBuffer(zero); 738 | 739 | InputVectorVolumeIteratorType vvIt(inputVectorVolume, inputVectorVolume->GetLargestPossibleRegion()); 740 | OutputVectorVolumeIteratorType fittedIt(fittedVolume, fittedVolume->GetLargestPossibleRegion()); 741 | 742 | MaskVolumeIteratorType mvIt(maskVolume, maskVolume->GetLargestPossibleRegion()); 743 | MapVolumeIteratorType rsqrIt(rsqrMap, rsqrMap->GetLargestPossibleRegion()); 744 | MapVolumeIteratorType ssdFittedIt(ssdFittedMap,ssdFittedMap->GetLargestPossibleRegion()); 745 | MapVolumeIteratorType ssdIt(ssdMap,ssdMap->GetLargestPossibleRegion()); 746 | MapVolumeIteratorType csFittedIt(csFittedMap,csFittedMap->GetLargestPossibleRegion()); 747 | MapVolumeIteratorType csIt(csMap,csMap->GetLargestPossibleRegion()); 748 | 749 | itk::LevenbergMarquardtOptimizer::Pointer optimizer = itk::LevenbergMarquardtOptimizer::New(); 750 | 751 | int cnt = 0; 752 | 753 | for(vvIt.GoToBegin();!vvIt.IsAtEnd();++vvIt){ 754 | //if(cnt>10) 755 | // break; 756 | VectorVolumeType::IndexType index = vvIt.GetIndex(); 757 | VectorVolumeType::PixelType vectorVoxel = vvIt.Get(); 758 | VectorVolumeType::PixelType fittedVoxel(vectorVoxel.GetSize()); 759 | for(int i=0;iSetX(bValuesPtr, bValuesSelected); 767 | const float* imageVector = const_cast(vectorVoxel.GetDataPointer()); 768 | int j = 0; 769 | for(int i=0;iSetNumberOfValues(numberOfSelectedPoints); 777 | 778 | costFunction->SetY(imageValuesPtr,bValuesSelected); 779 | 780 | DecayCostFunction::ParametersType initialValue = costFunction->GetInitialValue(); 781 | initialValue[0] = vectorVoxel[0]; 782 | DecayCostFunction::MeasureType temp = costFunction->GetValue(initialValue); 783 | optimizer->UseCostFunctionGradientOff(); 784 | optimizer->SetCostFunction(costFunction); 785 | 786 | itk::LevenbergMarquardtOptimizer::InternalOptimizerType *vnlOptimizer = optimizer->GetOptimizer(); 787 | vnlOptimizer->set_f_tolerance(1e-4f); 788 | vnlOptimizer->set_g_tolerance(1e-4f); 789 | vnlOptimizer->set_x_tolerance(1e-5f); 790 | vnlOptimizer->set_epsilon_function(1e-9f); 791 | vnlOptimizer->set_max_function_evals(200); 792 | 793 | try { 794 | optimizer->SetInitialPosition(initialValue); 795 | optimizer->StartOptimization(); 796 | } catch(itk::ExceptionObject &e) { 797 | std::cerr << " Exception caught: " << e << std::endl; 798 | } 799 | 800 | itk::LevenbergMarquardtOptimizer::ParametersType finalPosition; 801 | 802 | // fitted vector for all input b-values 803 | finalPosition = optimizer->GetCurrentPosition(); 804 | for(int i=0;iGetFittedValue(finalPosition, bValues[i]); 806 | } 807 | 808 | // fitted values for the b-values used in fitting only 809 | for(int i=0;iGetFittedValue(finalPosition,bValuesPtr[i]); 811 | } 812 | 813 | //std::cout << std::endl; 814 | fittedIt.Set(fittedVoxel); 815 | //std::cout << "Fitted voxel: " << fittedVoxel << " params: " << finalPosition << std::endl; 816 | switch(modelType){ 817 | case DecayCostFunction::BiExponential:{ 818 | parameterMapItVector[0].Set(finalPosition[0]); 819 | parameterMapItVector[1].Set(finalPosition[1]); 820 | parameterMapItVector[2].Set(finalPosition[2]*1e+6); 821 | parameterMapItVector[3].Set(finalPosition[3]*1e+6); 822 | break; 823 | } 824 | case DecayCostFunction::Kurtosis:{ 825 | parameterMapItVector[0].Set(finalPosition[0]); 826 | parameterMapItVector[1].Set(finalPosition[1]); 827 | parameterMapItVector[2].Set(finalPosition[2]*1e+6); 828 | break; 829 | } 830 | case DecayCostFunction::MonoExponential:{ 831 | parameterMapItVector[0].Set(finalPosition[0]); 832 | parameterMapItVector[1].Set(finalPosition[1]*1e+6); 833 | break; 834 | } 835 | case DecayCostFunction::StretchedExponential:{ 836 | parameterMapItVector[0].Set(finalPosition[0]); 837 | parameterMapItVector[1].Set(finalPosition[1]*1e+6); // DDC 838 | parameterMapItVector[2].Set(finalPosition[2]); // alpha 839 | break; 840 | } 841 | case DecayCostFunction::Gamma:{ 842 | parameterMapItVector[0].Set(finalPosition[0]); 843 | parameterMapItVector[1].Set(finalPosition[1]); // k 844 | parameterMapItVector[2].Set(finalPosition[2]*1e+6); // theta 845 | parameterMapItVector[3].Set((finalPosition[1]-1)*finalPosition[2]); // mode 846 | break; 847 | } 848 | 849 | default: abort(); 850 | } 851 | 852 | // initialize the rsqr map 853 | // see PkModeling/CLI/itkConcentrationToQuantitativeImageFilter.hxx:452 854 | { 855 | // Error measures are calculated separately for those b-values that were 856 | // used in the fitting process (*Fitted) and for all of the b-values 857 | // available in the data 858 | double rmsFitted = optimizer->GetOptimizer()->get_end_error(); 859 | double SSerrFitted = rmsFitted*rmsFitted*bValuesSelected; 860 | double SSerr = 0; 861 | double sumSquaredDifferences = 0, sumSquaredDifferencesFitted = 0; 862 | double sumSquared = 0.0, sumSquaredFitted = 0; 863 | double sum = 0.0, sumFitted = 0; 864 | double rSquared = 0.0; 865 | 866 | for (unsigned int i=0; i < bValuesSelected; ++i){ 867 | sumFitted += imageValuesPtr[i]; 868 | sumSquaredFitted += (imageValuesPtr[i]*imageValuesPtr[i]); 869 | sumSquaredDifferencesFitted += (imageValuesPtr[i]-fittedValuesPtr[i])*(imageValuesPtr[i]-fittedValuesPtr[i]); 870 | } 871 | 872 | for (unsigned int i=0; i < vectorVoxel.GetSize(); ++i){ 873 | sum += vectorVoxel[i]; 874 | sumSquared += (vectorVoxel[i]*vectorVoxel[i]); 875 | sumSquaredDifferences += (vectorVoxel[i]-fittedVoxel[i])*(vectorVoxel[i]-fittedVoxel[i]); 876 | } 877 | 878 | double SStotFitted = sumSquaredFitted - sumFitted*sumFitted/(double)bValuesSelected; 879 | double SStot = sumSquared - sum*sum/(double)vectorVoxel.GetSize(); 880 | 881 | rSquared = 1.0 - (SSerrFitted / SStotFitted); 882 | 883 | double chiSquaredNormFitted = sumSquaredDifferencesFitted/((double)bValuesSelected-numberOfMaps); 884 | double chiSquaredNorm = sumSquaredDifferences/((double)vectorVoxel.GetSize()-numberOfMaps); 885 | double chiSquaredFitted = 2.*sqrt(chiSquaredNormFitted); 886 | double chiSquared = 2.*sqrt(chiSquaredNorm); 887 | 888 | rsqrIt.Set(rSquared); 889 | ssdFittedIt.Set(sumSquaredDifferencesFitted); 890 | ssdIt.Set(sumSquaredDifferences); 891 | csIt.Set(chiSquared); 892 | csFittedIt.Set(chiSquaredFitted); 893 | } 894 | } 895 | 896 | for(int i=0;iSetMetaDataDictionary(inputVectorVolume->GetMetaDataDictionary()); 961 | writer->SetInput(fittedVolume); 962 | writer->SetFileName(fittedVolumeFileName.c_str()); 963 | writer->SetUseCompression(1); 964 | writer->Update(); 965 | } 966 | 967 | return EXIT_SUCCESS; 968 | } 969 | -------------------------------------------------------------------------------- /DWModeling/DWModeling.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Quantification 4 | DWModeling 5 | 6 | 0.0.1 7 | http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/DWModeling 8 | Slicer 9 | Andriy Fedorov (SPL/BWH) 10 | This work was partially funded by NIH grants R01 CA160902 (PI Maier) and U24 CA180918 (PIs Kikinis/Fedorov). 11 | 12 | 13 | Input parameters 14 | 15 | 16 | imageName 17 | 18 | input 19 | Input DWI MRI trace multivolume image 20 | 0 21 | 22 | 23 | 24 | modelName 25 | model 26 | 27 | Select the mathematical model used to fit the data 28 | BiExponential 29 | MonoExponential 30 | BiExponential 31 | Kurtosis 32 | Gamma 33 | StretchedExponential 34 | 35 | 36 | 37 | maskName 38 | mask 39 | 40 | input 41 | Input mask. Optional; if not specified, fitting will be done at all voxels. 42 | 43 | 44 | 45 | bValuesToInclude 46 | 47 | bvalInclude 48 | List of integers corresponding to the b-values that should be included in fitting data. Note that only one of the two lists (inlusion or exclusion) should be populated, or both can be empty, in which case all b-values will be used. Optional; if not defined, all b-values will be used. 49 | 50 | 51 | 52 | 53 | bValuesToExclude 54 | 55 | bvalExclude 56 | List of integers corresponding to the b-values that should be excluded from fitting data. Note that only one of the two lists (inlusion or exclusion) should be populated, or both can be empty, in which case all b-values will be used. Optional; if not defined, all b-values will be used. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | fittedVolumeFileName 67 | fittedVolume 68 | 69 | Output volume containing the values of the fitted function 70 | output 71 | 72 | 73 | 74 | rsqrVolumeFileName 75 | rsqrVolume 76 | 77 | Output volume containing the R^2 measure of the quality of fit. This measure is calculated only for the b-values used in the fitting process. 78 | output 79 | 80 | 81 | 82 | ssdFittedVolumeFileName 83 | ssdFittedVolume 84 | 85 | Volume with the pixel-wise sum of squared differences (SSD) map that takes into consideration only those b-values that were used in the fitting process 86 | output 87 | 88 | 89 | 90 | ssdVolumeFileName 91 | ssdVolume 92 | 93 | Volume with the pixel-wise sum of squared differences (SSD) map that takes into consideration all b-values. Note that this map will be identical to the previous one if the fitting procedure utilized all b-values 94 | output 95 | 96 | 97 | 98 | csFittedVolumeFileName 99 | csFittedVolume 100 | 101 | Volume with the pixel-wise chi squared map that takes into consideration only those b-values that were used in the fitting process 102 | output 103 | 104 | 105 | 106 | csVolumeFileName 107 | csVolume 108 | 109 | Volume with the pixel-wise pixel-wise chi squared map that takes into consideration all b-values. Note that this map will be identical to the previous one if the fitting procedure utilized all b-values 110 | output 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | adcMapFileName 120 | adcMonoExpDiff 121 | 122 | output 123 | Diffusion coefficient map of the mono-exponential model 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | slowDiffMapFileName 132 | slowDiff 133 | 134 | output 135 | Slow diffusion coefficient map of the bi-exponential model 136 | 137 | 138 | 139 | fastDiffMapFileName 140 | fastDiff 141 | 142 | output 143 | Fast diffusion coefficient map of the bi-exponential model 144 | 145 | 146 | 147 | fastDiffFractionMapFileName 148 | fastDiffFraction 149 | 150 | output 151 | Fast diffusion fraction map of the bi-exponential model 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | kurtosisDiffMapFileName 160 | kurtosisDiff 161 | 162 | output 163 | Diffusion coefficient map of the kurtosis model 164 | 165 | 166 | 167 | kurtosisMapFileName 168 | kurtosis 169 | 170 | output 171 | Kurtosos map 172 | 173 | 174 | 175 | 176 | 177 | See Oshio et al. 2014 178 | 179 | thetaMapFileName 180 | theta 181 | 182 | output 183 | Theta parameter of the gamma distribution model 184 | 185 | 186 | 187 | kMapFileName 188 | k 189 | 190 | output 191 | k map of the gamma distribution model 192 | 193 | 194 | 195 | modeMapFileName 196 | mode 197 | 198 | output 199 | Mode map of the gamma distribution model ( (k-1)*theta ) 200 | 201 | 202 | 203 | 204 | 205 | 206 | See Bennett et al. 2003 207 | 208 | DDCMapFileName 209 | ddc 210 | 211 | output 212 | Distributed Diffusion Coefficient map 213 | 214 | 215 | 216 | alphaMapFileName 217 | alpha 218 | 219 | output 220 | Stretching parameter (alpha) map 221 | 222 | 223 | 224 | 225 | 226 | Model-specific initial parameters 227 | 228 | 229 | monoExpInitParameters 230 | 231 | monoExpInitParameters 232 | List of initial model parameters in the following format (all numbers are floating point):initialScale,initialADC 233 | 0,0.0015 234 | 235 | 236 | 237 | biExpInitParameters 238 | 239 | biExpInitParameters 240 | List of initial model parameters in the following format (all numbers are floating point):initialScale,initialFastDiffusionFraction,initialSlowDiffusionCoefficient,initialFastDiffusionCoefficient 241 | 0,0.7,0.00025,0.002 242 | 243 | 244 | 245 | kurtosisInitParameters 246 | 247 | kurtosisInitParameters 248 | List of initial model parameters in the following format (all numbers are floating point):initialScale,initialKurtosis,initialKurtosisDiffusion 249 | 0,1,0.0015 250 | 251 | 252 | 253 | stretchedExpInitParameters 254 | 255 | stretchedExpInitParameters 256 | List of initial model parameters in the following format (all numbers are floating point):initialScale,initialDDC,initialAlpha 257 | 0,0.0017,0.7 258 | 259 | 260 | 261 | gammaInitParameters 262 | 263 | gammaInitParameters 264 | List of initial model parameters in the following format (all numbers are floating point):initialScale,initialK,initialTheta 265 | 0,1.5,0.002 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /DWModeling/Data/Baseline/MRModelingTest.nhdr.md5: -------------------------------------------------------------------------------- 1 | 3489f25641702e53a208565037b8d0b8 -------------------------------------------------------------------------------- /DWModeling/Data/Baseline/MRModelingTest.raw.md5: -------------------------------------------------------------------------------- 1 | 0749d4d3f07a217030f9ae33d94c4559 -------------------------------------------------------------------------------- /DWModeling/Data/Input/CTHeadAxial.nhdr.md5: -------------------------------------------------------------------------------- 1 | 6e5c289c73e14ba7a1b0f8aaf6ed249a -------------------------------------------------------------------------------- /DWModeling/Data/Input/CTHeadAxial.raw.gz.md5: -------------------------------------------------------------------------------- 1 | 3ebd710c9cf9d75750f4569b8caf6d07 -------------------------------------------------------------------------------- /DWModeling/Data/SampledPhantoms/00943_SER18/Input/00943_SER18.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/DWModeling/Data/SampledPhantoms/00943_SER18/Input/00943_SER18.nrrd -------------------------------------------------------------------------------- /DWModeling/Data/SampledPhantoms/00943_SER18/Input/Fitted volume frame 5-label.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/DWModeling/Data/SampledPhantoms/00943_SER18/Input/Fitted volume frame 5-label.nrrd -------------------------------------------------------------------------------- /DWModeling/Data/SampledPhantoms/00943_SER18/README: -------------------------------------------------------------------------------- 1 | The phantom was generated using probing.nrrd label with following parameters: numLabels = 7, patchSize = 3, maxSamples = 5 2 | -------------------------------------------------------------------------------- /DWModeling/Data/SampledPhantoms/00943_SER18/probing.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/DWModeling/Data/SampledPhantoms/00943_SER18/probing.nrrd -------------------------------------------------------------------------------- /DWModeling/Data/SampledPhantoms/README: -------------------------------------------------------------------------------- 1 | This directory contains phantoms generated from real data using Util/makePhantom.py script from [PK Modelling Extension](https://github.com/millerjv/PkModeling). 2 | -------------------------------------------------------------------------------- /DWModeling/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_SUBDIRECTORY(Cxx) 2 | -------------------------------------------------------------------------------- /DWModeling/Testing/Cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(Launcher_Command ${Slicer_LAUNCH_COMMAND}) 4 | set(BASELINE ${CMAKE_CURRENT_SOURCE_DIR}/../../Data/Baseline) 5 | set(INPUT ${CMAKE_CURRENT_SOURCE_DIR}/../../Data/Input) 6 | set(TEMP "${CMAKE_BINARY_DIR}/Testing/Temporary") 7 | 8 | set(CLP ${MODULE_NAME}) 9 | 10 | #----------------------------------------------------------------------------- 11 | add_executable(${CLP}Test ${CLP}Test.cxx) 12 | target_link_libraries(${CLP}Test ${CLP}Lib) 13 | set_target_properties(${CLP}Test PROPERTIES LABELS ${CLP}) 14 | 15 | #----------------------------------------------------------------------------- 16 | set(testname ${CLP}Test) 17 | ExternalData_add_test(${CLP}Data NAME ${testname} COMMAND ${Launcher_Command} $ 18 | --compare DATA{${BASELINE}/${CLP}Test.nhdr,${CLP}Test.raw} 19 | ${TEMP}/${CLP}Test.nhdr 20 | ModuleEntryPoint 21 | --sigma 2.5 DATA{${INPUT}/CTHeadAxial.nhdr,CTHeadAxial.raw.gz} ${TEMP}/${CLP}Test.nhdr 22 | ) 23 | set_property(TEST ${testname} PROPERTY LABELS ${CLP}) 24 | 25 | #----------------------------------------------------------------------------- 26 | ExternalData_add_target(${CLP}Data) 27 | -------------------------------------------------------------------------------- /DWModeling/Testing/Cxx/MRModelingTest.cxx: -------------------------------------------------------------------------------- 1 | #if defined(_MSC_VER) 2 | #pragma warning ( disable : 4786 ) 3 | #endif 4 | 5 | #ifdef __BORLANDC__ 6 | #define ITK_LEAN_AND_MEAN 7 | #endif 8 | 9 | #include "itkTestMain.h" 10 | 11 | // STD includes 12 | #include 13 | 14 | #ifdef WIN32 15 | # define MODULE_IMPORT __declspec(dllimport) 16 | #else 17 | # define MODULE_IMPORT 18 | #endif 19 | 20 | extern "C" MODULE_IMPORT int ModuleEntryPoint(int, char* []); 21 | 22 | void RegisterTests() 23 | { 24 | StringToTestFunctionMap["ModuleEntryPoint"] = ModuleEntryPoint; 25 | } 26 | -------------------------------------------------------------------------------- /DistanceMapBasedRegistration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | set(MODULE_NAME DistanceMapBasedRegistration) 3 | 4 | #----------------------------------------------------------------------------- 5 | set(MODULE_PYTHON_SCRIPTS 6 | ${MODULE_NAME}.py 7 | ) 8 | 9 | set(MODULE_PYTHON_RESOURCES 10 | Resources/Icons/${MODULE_NAME}.png 11 | ) 12 | 13 | #----------------------------------------------------------------------------- 14 | slicerMacroBuildScriptedModule( 15 | NAME ${MODULE_NAME} 16 | SCRIPTS ${MODULE_PYTHON_SCRIPTS} 17 | RESOURCES ${MODULE_PYTHON_RESOURCES} 18 | WITH_GENERIC_TESTS 19 | ) 20 | 21 | #----------------------------------------------------------------------------- 22 | if(BUILD_TESTING) 23 | 24 | # Register the unittest subclass in the main script as a ctest. 25 | # Note that the test will also be available at runtime. 26 | slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py) 27 | 28 | # Additional build-time testing 29 | add_subdirectory(Testing) 30 | endif() 31 | -------------------------------------------------------------------------------- /DistanceMapBasedRegistration/DistanceMapBasedRegistration.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from __main__ import vtk, qt, ctk, slicer 4 | from slicer.ScriptedLoadableModule import * 5 | import logging 6 | 7 | import SimpleITK as sitk 8 | import sitkUtils 9 | 10 | # 11 | # DistanceMapBasedRegistration 12 | # 13 | 14 | class DistanceMapBasedRegistration(ScriptedLoadableModule): 15 | """Uses ScriptedLoadableModule base class, available at: 16 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 17 | """ 18 | 19 | def __init__(self, parent): 20 | ScriptedLoadableModule.__init__(self, parent) 21 | self.parent.title = "Distance Map Based Registration" 22 | self.parent.categories = ["Registration.Label Registration"] 23 | self.parent.dependencies = ['SegmentationSmoothing','QuadEdgeSurfaceMesher'] 24 | self.parent.contributors = ["Andrey Fedorov (BWH), Andras Lasso (Queen's University)"] 25 | self.parent.helpText = """ 26 | This module performs distance-based image registration using segmentations 27 | of the structure of interest. The structure should be segmented in both fixed and moving images. 28 | The actual moving and fixed images are optional, and if available will be 29 | used to generate registered image and visualize the results. 30 | See 31 | online documentation for details. 32 | """ 33 | self.parent.acknowledgementText = """ 34 | Development of this module was supported in part by NIH through grants 35 | R01 CA111288, P41 RR019703 and U24 CA180918. 36 | """ 37 | 38 | # 39 | # DistanceMapBasedRegistrationWidget 40 | # 41 | 42 | class DistanceMapBasedRegistrationWidget(ScriptedLoadableModuleWidget): 43 | """Uses ScriptedLoadableModuleWidget base class, available at: 44 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 45 | """ 46 | 47 | def setup(self): 48 | ScriptedLoadableModuleWidget.setup(self) 49 | 50 | # Instantiate and connect widgets ... 51 | 52 | # 53 | # Parameters Area 54 | # 55 | parametersCollapsibleButton = ctk.ctkCollapsibleButton() 56 | parametersCollapsibleButton.text = "Parameters" 57 | self.layout.addWidget(parametersCollapsibleButton) 58 | 59 | # Layout within the dummy collapsible button 60 | parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton) 61 | 62 | # 63 | # fixed image selector 64 | # 65 | self.fixedImageSelector = slicer.qMRMLNodeComboBox() 66 | self.fixedImageSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" ) 67 | self.fixedImageSelector.selectNodeUponCreation = True 68 | self.fixedImageSelector.addEnabled = False 69 | self.fixedImageSelector.removeEnabled = False 70 | self.fixedImageSelector.noneEnabled = True 71 | self.fixedImageSelector.showHidden = False 72 | self.fixedImageSelector.showChildNodeTypes = False 73 | self.fixedImageSelector.setMRMLScene( slicer.mrmlScene ) 74 | self.fixedImageSelector.setToolTip( "Fixed image (optional)" ) 75 | parametersFormLayout.addRow("Fixed Image: ", self.fixedImageSelector) 76 | 77 | # 78 | # fixed image label selector 79 | # 80 | self.fixedImageLabelSelector = slicer.qMRMLNodeComboBox() 81 | self.fixedImageLabelSelector.nodeTypes = ( ("vtkMRMLLabelMapVolumeNode"), "" ) 82 | self.fixedImageLabelSelector.selectNodeUponCreation = True 83 | self.fixedImageLabelSelector.addEnabled = False 84 | self.fixedImageLabelSelector.removeEnabled = False 85 | self.fixedImageLabelSelector.noneEnabled = False 86 | self.fixedImageLabelSelector.showHidden = False 87 | self.fixedImageLabelSelector.showChildNodeTypes = False 88 | self.fixedImageLabelSelector.setMRMLScene( slicer.mrmlScene ) 89 | self.fixedImageLabelSelector.setToolTip( "Segmentation of the fixed image" ) 90 | parametersFormLayout.addRow("Segmentation of the fixed Image: ", self.fixedImageLabelSelector) 91 | 92 | # 93 | # moving image selector 94 | # 95 | self.movingImageSelector = slicer.qMRMLNodeComboBox() 96 | self.movingImageSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" ) 97 | self.movingImageSelector.selectNodeUponCreation = True 98 | self.movingImageSelector.addEnabled = False 99 | self.movingImageSelector.removeEnabled = False 100 | self.movingImageSelector.noneEnabled = True 101 | self.movingImageSelector.showHidden = False 102 | self.movingImageSelector.showChildNodeTypes = False 103 | self.movingImageSelector.setMRMLScene( slicer.mrmlScene ) 104 | self.movingImageSelector.setToolTip( "Moving image (optional)" ) 105 | parametersFormLayout.addRow("Moving Image: ", self.movingImageSelector) 106 | 107 | # 108 | # moving image label selector 109 | # 110 | self.movingImageLabelSelector = slicer.qMRMLNodeComboBox() 111 | self.movingImageLabelSelector.nodeTypes = ( ("vtkMRMLLabelMapVolumeNode"), "" ) 112 | self.movingImageLabelSelector.selectNodeUponCreation = True 113 | self.movingImageLabelSelector.addEnabled = False 114 | self.movingImageLabelSelector.removeEnabled = False 115 | self.movingImageLabelSelector.noneEnabled = False 116 | self.movingImageLabelSelector.showHidden = False 117 | self.movingImageLabelSelector.showChildNodeTypes = False 118 | self.movingImageLabelSelector.setMRMLScene( slicer.mrmlScene ) 119 | self.movingImageLabelSelector.setToolTip( "Segmentation of the moving image" ) 120 | parametersFormLayout.addRow("Segmentation of the moving Image: ", self.movingImageLabelSelector) 121 | 122 | # 123 | # Affine output transform selector 124 | # 125 | self.affineTransformSelector = slicer.qMRMLNodeComboBox() 126 | self.affineTransformSelector.nodeTypes = ( ("vtkMRMLTransformNode"), "" ) 127 | self.affineTransformSelector.selectNodeUponCreation = True 128 | self.affineTransformSelector.addEnabled = True 129 | self.affineTransformSelector.removeEnabled = False 130 | self.affineTransformSelector.noneEnabled = False 131 | self.affineTransformSelector.showHidden = False 132 | self.affineTransformSelector.showChildNodeTypes = False 133 | self.affineTransformSelector.baseName = 'Affine Transform' 134 | self.affineTransformSelector.setMRMLScene( slicer.mrmlScene ) 135 | self.affineTransformSelector.setToolTip( "Registration affine transform" ) 136 | parametersFormLayout.addRow("Registration affine transform: ", self.affineTransformSelector) 137 | 138 | # 139 | # B-spline output transform selector 140 | # 141 | self.bsplineTransformSelector = slicer.qMRMLNodeComboBox() 142 | self.bsplineTransformSelector.nodeTypes = ( ("vtkMRMLTransformNode"), "" ) 143 | self.bsplineTransformSelector.selectNodeUponCreation = True 144 | self.bsplineTransformSelector.addEnabled = True 145 | self.bsplineTransformSelector.removeEnabled = False 146 | self.bsplineTransformSelector.noneEnabled = False 147 | self.bsplineTransformSelector.showHidden = False 148 | self.bsplineTransformSelector.showChildNodeTypes = False 149 | self.bsplineTransformSelector.baseName = 'Deformable Transform' 150 | self.bsplineTransformSelector.setMRMLScene( slicer.mrmlScene ) 151 | self.bsplineTransformSelector.setToolTip( "Registration b-spline transform" ) 152 | parametersFormLayout.addRow("Registration B-spline Transform: ", self.bsplineTransformSelector) 153 | 154 | # 155 | # registered volume selector 156 | # 157 | ''' 158 | self.outputImageSelector = slicer.qMRMLNodeComboBox() 159 | self.outputImageSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" ) 160 | # self.outputImageSelector.nodeTypes = ( ("vtkMRMLLabelMapVolumeNode"), "" ) 161 | self.outputImageSelector.selectNodeUponCreation = True 162 | self.outputImageSelector.addEnabled = True 163 | self.outputImageSelector.removeEnabled = True 164 | self.outputImageSelector.noneEnabled = True 165 | self.outputImageSelector.showHidden = False 166 | self.outputImageSelector.showChildNodeTypes = False 167 | self.outputImageSelector.baseName = 'Registered Volume' 168 | self.outputImageSelector.setMRMLScene( slicer.mrmlScene ) 169 | self.outputImageSelector.setToolTip( "Registered volume (will be generated only if the moving image was provided)" ) 170 | parametersFormLayout.addRow("Registered Volume: ", self.outputImageSelector) 171 | ''' 172 | 173 | # 174 | # To be added later: advanced parameters 175 | # registration modes (rigid/affine/bspline), save rigid/affine transforms, 176 | # crop box margin, number of samples, ... 177 | # 178 | # Add parameter node to facilitate registration from other modules and 179 | # command line 180 | # 181 | 182 | self.registrationModeGroup = qt.QButtonGroup() 183 | self.noRegistrationRadio = qt.QRadioButton('Before registration') 184 | self.linearRegistrationRadio = qt.QRadioButton('After linear registration') 185 | self.deformableRegistrationRadio = qt.QRadioButton('After deformable registration') 186 | self.noRegistrationRadio.setChecked(1) 187 | self.registrationModeGroup.addButton(self.noRegistrationRadio,1) 188 | self.registrationModeGroup.addButton(self.linearRegistrationRadio,2) 189 | self.registrationModeGroup.addButton(self.deformableRegistrationRadio,3) 190 | parametersFormLayout.addRow(qt.QLabel("Visualization")) 191 | parametersFormLayout.addRow("",self.noRegistrationRadio) 192 | parametersFormLayout.addRow("",self.linearRegistrationRadio) 193 | parametersFormLayout.addRow("",self.deformableRegistrationRadio) 194 | 195 | self.registrationModeGroup.connect('buttonClicked(int)',self.onVisualizationModeClicked) 196 | 197 | # 198 | # Apply Button 199 | # 200 | self.applyButton = qt.QPushButton("Apply") 201 | self.applyButton.toolTip = "Run the algorithm." 202 | self.applyButton.enabled = True 203 | parametersFormLayout.addRow(self.applyButton) 204 | 205 | # connections 206 | self.applyButton.connect('clicked(bool)', self.onApplyButton) 207 | 208 | # Add vertical spacer 209 | self.layout.addStretch(1) 210 | 211 | # Refresh Apply button state 212 | #self.onSelect() 213 | 214 | self.parameterNode = slicer.vtkMRMLScriptedModuleNode() 215 | 216 | ''' 217 | TODO: 218 | * improve GUI structure - separate parameters and visualization 219 | * improve interaction signal/slots 220 | 221 | ''' 222 | 223 | def cleanup(self): 224 | pass 225 | 226 | def onSelect(self): 227 | # self.applyButton.enabled = self.inputSelector.currentNode() and self.outputSelector.currentNode() 228 | pass 229 | 230 | def onApplyButton(self): 231 | logic = DistanceMapBasedRegistrationLogic() 232 | 233 | if self.fixedImageSelector.currentNode(): 234 | self.parameterNode.SetAttribute('FixedImageNodeID', self.fixedImageSelector.currentNode().GetID()) 235 | if self.fixedImageLabelSelector.currentNode(): 236 | self.parameterNode.SetAttribute('FixedLabelNodeID', self.fixedImageLabelSelector.currentNode().GetID()) 237 | if self.movingImageSelector.currentNode(): 238 | self.parameterNode.SetAttribute('MovingImageNodeID', self.movingImageSelector.currentNode().GetID()) 239 | if self.movingImageLabelSelector.currentNode(): 240 | self.parameterNode.SetAttribute('MovingLabelNodeID', self.movingImageLabelSelector.currentNode().GetID()) 241 | ''' 242 | if self.outputImageSelector.currentNode(): 243 | self.parameterNode.SetAttribute('OutputVolumeNodeID', self.outputImageSelector.currentNode().GetID()) 244 | ''' 245 | if self.affineTransformSelector.currentNode(): 246 | self.parameterNode.SetAttribute('AffineTransformNodeID', self.affineTransformSelector.currentNode().GetID()) 247 | if self.bsplineTransformSelector.currentNode(): 248 | self.parameterNode.SetAttribute('BSplineTransformNodeID', self.bsplineTransformSelector.currentNode().GetID()) 249 | logic.run(self.parameterNode) 250 | 251 | # resample moving volume 252 | # logic.resample(self.parameterNode) 253 | 254 | # configure the GUI 255 | logic.showResults(self.parameterNode) 256 | self.noRegistrationRadio.checked = 1 257 | self.onVisualizationModeClicked(1) 258 | 259 | return 260 | 261 | def onVisualizationModeClicked(self,mode): 262 | 263 | if self.parameterNode.GetAttribute('MovingImageNodeID'): 264 | movingVolume = slicer.mrmlScene.GetNodeByID(self.parameterNode.GetAttribute('MovingImageNodeID')) 265 | else: 266 | movingVolume = slicer.mrmlScene.GetNodeByID(self.parameterNode.GetAttribute('MovingLabelDistanceMapID')) 267 | 268 | movingSurface = slicer.mrmlScene.GetNodeByID(self.parameterNode.GetAttribute('MovingLabelSurfaceID')) 269 | 270 | affineTransform = slicer.mrmlScene.GetNodeByID(self.parameterNode.GetAttribute('AffineTransformNodeID')) 271 | bsplineTransform = slicer.mrmlScene.GetNodeByID(self.parameterNode.GetAttribute('BSplineTransformNodeID')) 272 | affineDisplayNode = affineTransform.GetDisplayNode() 273 | bsplineDisplayNode = bsplineTransform.GetDisplayNode() 274 | 275 | if mode == 1: 276 | movingVolume.SetAndObserveTransformNodeID('') 277 | movingSurface.SetAndObserveTransformNodeID('') 278 | affineDisplayNode.SetSliceIntersectionVisibility(0) 279 | bsplineDisplayNode.SetSliceIntersectionVisibility(0) 280 | if mode == 2: 281 | movingVolume.SetAndObserveTransformNodeID(affineTransform.GetID()) 282 | movingSurface.SetAndObserveTransformNodeID(affineTransform.GetID()) 283 | affineDisplayNode.SetSliceIntersectionVisibility(1) 284 | bsplineDisplayNode.SetSliceIntersectionVisibility(0) 285 | affineDisplayNode.SetVisualizationMode(1) 286 | if mode == 3: 287 | movingVolume.SetAndObserveTransformNodeID(bsplineTransform.GetID()) 288 | movingSurface.SetAndObserveTransformNodeID(bsplineTransform.GetID()) 289 | affineDisplayNode.SetSliceIntersectionVisibility(0) 290 | bsplineDisplayNode.SetSliceIntersectionVisibility(1) 291 | bsplineDisplayNode.SetVisualizationMode(1) 292 | return 293 | 294 | # 295 | # DistanceMapBasedRegistrationLogic 296 | # 297 | 298 | class DistanceMapBasedRegistrationLogic(ScriptedLoadableModuleLogic): 299 | """This class should implement all the actual 300 | computation done by your module. The interface 301 | should be such that other python code can import 302 | this class and make use of the functionality without 303 | requiring an instance of the Widget. 304 | Uses ScriptedLoadableModuleLogic base class, available at: 305 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 306 | """ 307 | 308 | def hasImageData(self,volumeNode): 309 | """This is an example logic method that 310 | returns true if the passed in volume 311 | node has valid image data 312 | """ 313 | if not volumeNode: 314 | logging.debug('hasImageData failed: no volume node') 315 | return False 316 | if volumeNode.GetImageData() == None: 317 | logging.debug('hasImageData failed: no image data in volume node') 318 | return False 319 | return True 320 | 321 | def isValidInputOutputData(self, inputVolumeNode, outputVolumeNode): 322 | """Validates if the output is not the same as input 323 | """ 324 | if not inputVolumeNode: 325 | logging.debug('isValidInputOutputData failed: no input volume node defined') 326 | return False 327 | if not outputVolumeNode: 328 | logging.debug('isValidInputOutputData failed: no output volume node defined') 329 | return False 330 | if inputVolumeNode.GetID()==outputVolumeNode.GetID(): 331 | logging.debug('isValidInputOutputData failed: input and output volume is the same. Create a new volume for output to avoid this error.') 332 | return False 333 | return True 334 | 335 | def run(self, parameterNode): 336 | """ 337 | Run the actual algorithm 338 | """ 339 | 340 | ''' 341 | if not self.isValidInputOutputData(inputVolume, outputVolume): 342 | slicer.util.errorDisplay('Input volume is the same as output volume. Choose a different output volume.') 343 | return False 344 | ''' 345 | 346 | fixedLabelNodeID = parameterNode.GetAttribute('FixedLabelNodeID') 347 | movingLabelNodeID = parameterNode.GetAttribute('MovingLabelNodeID') 348 | outputVolumeNodeID = parameterNode.GetAttribute('OutputVolumeNodeID') 349 | affineTransformNode = slicer.mrmlScene.GetNodeByID(parameterNode.GetAttribute('AffineTransformNodeID')) 350 | bsplineTransformNode = slicer.mrmlScene.GetNodeByID(parameterNode.GetAttribute('BSplineTransformNodeID')) 351 | 352 | # crop the labels 353 | import SimpleITK as sitk 354 | import sitkUtils 355 | 356 | (bbMin,bbMax) = self.getBoundingBox(fixedLabelNodeID, movingLabelNodeID) 357 | 358 | logging.info("Before preprocessing") 359 | 360 | fixedLabelDistanceMap = self.preProcessLabel(fixedLabelNodeID, bbMin, bbMax) 361 | parameterNode.SetAttribute('FixedLabelDistanceMapID',fixedLabelDistanceMap.GetID()) 362 | fixedLabelSmoothed = slicer.util.getNode(slicer.mrmlScene.GetNodeByID(fixedLabelNodeID).GetName()+'-Smoothed') 363 | parameterNode.SetAttribute('FixedLabelSmoothedID',fixedLabelSmoothed.GetID()) 364 | 365 | logging.info('Fixed label processing done') 366 | 367 | movingLabelDistanceMap = self.preProcessLabel(movingLabelNodeID, bbMin, bbMax) 368 | parameterNode.SetAttribute('MovingLabelDistanceMapID',movingLabelDistanceMap.GetID()) 369 | movingLabelSmoothed = slicer.util.getNode(slicer.mrmlScene.GetNodeByID(movingLabelNodeID).GetName()+'-Smoothed') 370 | parameterNode.SetAttribute('MovingLabelSmoothedID',movingLabelSmoothed.GetID()) 371 | logging.info('Moving label processing done') 372 | 373 | # run registration 374 | 375 | registrationParameters = {'fixedVolume':fixedLabelDistanceMap.GetID(), 'movingVolume':movingLabelDistanceMap.GetID(),'useRigid':True,'useAffine':True,'numberOfSamples':'10000','costMetric':'MSE','outputTransform':affineTransformNode.GetID()} 376 | slicer.cli.run(slicer.modules.brainsfit, None, registrationParameters, wait_for_completion=True) 377 | 378 | parameterNode.SetAttribute('AffineTransformNodeID',affineTransformNode.GetID()) 379 | 380 | logging.info('affineRegistrationCompleted!') 381 | 382 | registrationParameters = {'fixedVolume':fixedLabelDistanceMap.GetID(), 'movingVolume':movingLabelDistanceMap.GetID(),'useBSpline':True,'splineGridSize':'3,3,3','numberOfSamples':'10000','costMetric':'MSE','bsplineTransform':bsplineTransformNode.GetID(),'initialTransform':affineTransformNode.GetID()} 383 | slicer.cli.run(slicer.modules.brainsfit, None, registrationParameters, wait_for_completion=True) 384 | 385 | parameterNode.SetAttribute('BSplineTransformNodeID',bsplineTransformNode.GetID()) 386 | 387 | logging.info('bsplineRegistrationCompleted!') 388 | 389 | logging.info('Processing completed') 390 | 391 | return True 392 | 393 | def showResults(self,parameterNode): 394 | # duplicate moving volume 395 | 396 | self.makeSurfaceModels(parameterNode) 397 | 398 | volumesLogic = slicer.modules.volumes.logic() 399 | movingImageID = parameterNode.GetAttribute('MovingImageNodeID') 400 | if not movingImageID: 401 | movingImageID = parameterNode.GetAttribute('MovingLabelDistanceMapID') 402 | 403 | fixedImageID = parameterNode.GetAttribute('FixedImageNodeID') 404 | if not fixedImageID: 405 | fixedImageID = parameterNode.GetAttribute('FixedLabelDistanceMapID') 406 | 407 | movingImageNode = slicer.mrmlScene.GetNodeByID(movingImageID) 408 | 409 | # display intersection of the fixed label surface in all slices 410 | fixedLabelSurface = slicer.mrmlScene.GetNodeByID(parameterNode.GetAttribute('FixedLabelSurfaceID')) 411 | movingLabelSurface = slicer.mrmlScene.GetNodeByID(parameterNode.GetAttribute('MovingLabelSurfaceID')) 412 | fixedModelDisplayNode = fixedLabelSurface.GetDisplayNode() 413 | movingModelDisplayNode = movingLabelSurface.GetDisplayNode() 414 | 415 | fixedModelDisplayNode.SetSliceIntersectionVisibility(1) 416 | fixedModelDisplayNode.SetSliceIntersectionThickness(3) 417 | 418 | movingModelDisplayNode.SetSliceIntersectionVisibility(1) 419 | movingModelDisplayNode.SetSliceIntersectionThickness(3) 420 | 421 | 422 | movingImageCloneID = parameterNode.GetAttribute('MovingImageCloneID') 423 | if movingImageCloneID: 424 | slicer.mrmlScene.RemoveNode(slicer.mrmlScene.GetNodeByID(movingImageCloneID)) 425 | 426 | movingImageClone = volumesLogic.CloneVolume(movingImageNode,'MovingImageCopy') 427 | parameterNode.SetAttribute('MovingImageCloneID',movingImageClone.GetID()) 428 | 429 | lm = slicer.app.layoutManager() 430 | lm.setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView) 431 | 432 | sliceCompositeNodes = slicer.mrmlScene.GetNodesByClass('vtkMRMLSliceCompositeNode') 433 | sliceCompositeNodes.SetReferenceCount(sliceCompositeNodes.GetReferenceCount()-1) 434 | 435 | for i in range(sliceCompositeNodes.GetNumberOfItems()): 436 | scn = sliceCompositeNodes.GetItemAsObject(i) 437 | scn.SetForegroundVolumeID(fixedImageID) 438 | scn.SetBackgroundVolumeID(movingImageID) 439 | scn.SetLabelVolumeID('') 440 | 441 | # TODO: call the code to configure views based on the mode selected 442 | 443 | return 444 | 445 | # create surface model for the moving volume segmentation 446 | # apply transforms based on user input (none, affine, deformable) 447 | # (need to create another panel in the GUI for visualization) 448 | # enable transform visualization in-slice and 3d 449 | 450 | def makeSurfaceModels(self,parameterNode): 451 | fixedLabel = slicer.util.getNode(parameterNode.GetAttribute('FixedLabelNodeID')) 452 | movingLabel = slicer.util.getNode(parameterNode.GetAttribute('MovingLabelNodeID')) 453 | 454 | fixedModelID = parameterNode.GetAttribute('FixedLabelSurfaceID') 455 | if fixedModelID: 456 | fixedModel = slicer.util.getNode(fixedModelID) 457 | logging.info('Reusing existing model: '+fixedModelID+' '+fixedModel.GetName()) 458 | else: 459 | fixedModel = slicer.vtkMRMLModelNode() 460 | slicer.mrmlScene.AddNode(fixedModel) 461 | fixedModel.SetName(fixedLabel.GetName()+'-surface') 462 | parameterNode.SetAttribute('FixedLabelSurfaceID',fixedModel.GetID()) 463 | logging.info('Created a new model: '+fixedModel.GetID()+' '+fixedModel.GetName()) 464 | 465 | parameters = {'inputImageName':parameterNode.GetAttribute('FixedLabelSmoothedID'),'outputMeshName':fixedModel.GetID()} 466 | slicer.cli.run(slicer.modules.quadedgesurfacemesher,None,parameters,wait_for_completion=True) 467 | fixedModel.GetDisplayNode().SetColor(0.9,0.9,0) 468 | 469 | movingModelID = parameterNode.GetAttribute('MovingLabelSurfaceID') 470 | if movingModelID: 471 | movingModel = slicer.util.getNode(movingModelID) 472 | logging.info('Reusing existing model: '+movingModelID+' '+movingModelID.GetName()) 473 | else: 474 | movingModel = slicer.vtkMRMLModelNode() 475 | slicer.mrmlScene.AddNode(movingModel) 476 | movingModel.SetName(movingLabel.GetName()+'-surface') 477 | parameterNode.SetAttribute('MovingLabelSurfaceID',movingModel.GetID()) 478 | logging.info('Created a new model: '+movingModel.GetID()+' '+movingModel.GetName()) 479 | 480 | parameters = {'inputImageName':parameterNode.GetAttribute('MovingLabelSmoothedID'),'outputMeshName':movingModel.GetID()} 481 | slicer.cli.run(slicer.modules.quadedgesurfacemesher,None,parameters,wait_for_completion=True) 482 | movingModel.GetDisplayNode().SetColor(0,0.7,0.9) 483 | 484 | return 485 | 486 | def getBoundingBox(self,fixedLabelNodeID,movingLabelNodeID): 487 | 488 | ls = sitk.LabelStatisticsImageFilter() 489 | 490 | fixedLabelNode = slicer.mrmlScene.GetNodeByID(fixedLabelNodeID) 491 | movingLabelNode = slicer.mrmlScene.GetNodeByID(movingLabelNodeID) 492 | 493 | fixedLabelAddress = sitkUtils.GetSlicerITKReadWriteAddress(fixedLabelNode.GetName()) 494 | movingLabelAddress = sitkUtils.GetSlicerITKReadWriteAddress(movingLabelNode.GetName()) 495 | 496 | fixedLabelImage = sitk.ReadImage(fixedLabelAddress) 497 | movingLabelImage = sitk.ReadImage(movingLabelAddress) 498 | 499 | cast = sitk.CastImageFilter() 500 | cast.SetOutputPixelType(2) 501 | unionLabelImage = (cast.Execute(fixedLabelImage) + cast.Execute(movingLabelImage)) > 0 502 | unionLabelImage = cast.Execute(unionLabelImage) 503 | 504 | ls.Execute(unionLabelImage,unionLabelImage) 505 | bb = ls.GetBoundingBox(1) 506 | logging.info('Bounding box:'+str(bb)) 507 | 508 | size = unionLabelImage.GetSize() 509 | bbMin = (max(0,bb[0]-30),max(0,bb[2]-30),max(0,bb[4]-5)) 510 | bbMax = (size[0]-min(size[0],bb[1]+30),size[1]-min(size[1],bb[3]+30),size[2]-(min(size[2],bb[5]+5))) 511 | 512 | return (bbMin,bbMax) 513 | 514 | def preProcessLabel(self,labelNodeID,bbMin,bbMax): 515 | 516 | logging.info('Label node ID: '+labelNodeID) 517 | 518 | labelNode = slicer.util.getNode(labelNodeID) 519 | 520 | labelNodeAddress = sitkUtils.GetSlicerITKReadWriteAddress(labelNode.GetName()) 521 | 522 | logging.info('Label node address: '+str(labelNodeAddress)) 523 | 524 | labelImage = sitk.ReadImage(labelNodeAddress) 525 | 526 | logging.info('Read image: '+str(labelImage)) 527 | 528 | crop = sitk.CropImageFilter() 529 | crop.SetLowerBoundaryCropSize(bbMin) 530 | crop.SetUpperBoundaryCropSize(bbMax) 531 | croppedImage = crop.Execute(labelImage) 532 | 533 | logging.info('Cropped image done: '+str(croppedImage)) 534 | 535 | croppedLabelName = labelNode.GetName()+'-Cropped' 536 | sitkUtils.PushVolumeToSlicer(croppedImage,name=croppedLabelName) 537 | logging.info('Cropped volume pushed') 538 | 539 | croppedLabel = slicer.util.getNode(croppedLabelName) 540 | 541 | logging.info('Smoothed image done') 542 | 543 | smoothLabelName = labelNode.GetName()+'-Smoothed' 544 | smoothLabel = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode") 545 | smoothLabel.SetName(smoothLabelName) 546 | smoothLabel.CreateDefaultStorageNode() 547 | 548 | # smooth the labels 549 | smoothingParameters = {'inputImageName':croppedLabel.GetID(), 'outputImageName':smoothLabel.GetID()} 550 | logging.info('Smoothing parameters:'+str(smoothingParameters)) 551 | cliNode = slicer.cli.run(slicer.modules.segmentationsmoothing, None, smoothingParameters, wait_for_completion = True) 552 | 553 | # crop the bounding box 554 | 555 | ''' 556 | TODO: 557 | * output volume node probably not needed here 558 | * intermediate nodes should probably be hidden 559 | ''' 560 | 561 | dt = sitk.SignedMaurerDistanceMapImageFilter() 562 | dt.SetSquaredDistance(False) 563 | distanceMapName = labelNode.GetName()+'-DistanceMap' 564 | logging.info('Reading smoothed image: '+smoothLabel.GetID()) 565 | smoothLabelAddress = sitkUtils.GetSlicerITKReadWriteAddress(smoothLabel.GetName()) 566 | smoothLabelImage = sitk.ReadImage(smoothLabelAddress) 567 | distanceImage = dt.Execute(smoothLabelImage) 568 | sitkUtils.PushVolumeToSlicer(distanceImage, name=distanceMapName) 569 | 570 | return slicer.util.getNode(distanceMapName) 571 | 572 | 573 | class DistanceMapBasedRegistrationTest(ScriptedLoadableModuleTest): 574 | """ 575 | This is the test case for your scripted module. 576 | Uses ScriptedLoadableModuleTest base class, available at: 577 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 578 | """ 579 | 580 | def setUp(self): 581 | """ Do whatever is needed to reset the state - typically a scene clear will be enough. 582 | """ 583 | slicer.mrmlScene.Clear(0) 584 | 585 | def runTest(self): 586 | """Run as few or as many tests as needed here. 587 | """ 588 | self.setUp() 589 | self.test_DistanceMapBasedRegistration1() 590 | 591 | def test_DistanceMapBasedRegistration1(self): 592 | """ Ideally you should have several levels of tests. At the lowest level 593 | tests should exercise the functionality of the logic with different inputs 594 | (both valid and invalid). At higher levels your tests should emulate the 595 | way the user would interact with your code and confirm that it still works 596 | the way you intended. 597 | One of the most important features of the tests is that it should alert other 598 | developers when their changes will have an impact on the behavior of your 599 | module. For example, if a developer removes a feature that you depend on, 600 | your test should break so they know that the feature is needed. 601 | """ 602 | 603 | self.delayDisplay("Starting the test") 604 | # 605 | # first, get some data 606 | # 607 | import urllib 608 | downloads = ( 609 | ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume), 610 | ) 611 | 612 | for url,name,loader in downloads: 613 | filePath = slicer.app.temporaryPath + '/' + name 614 | if not os.path.exists(filePath) or os.stat(filePath).st_size == 0: 615 | logging.info('Requesting download %s from %s...\n' % (name, url)) 616 | urllib.urlretrieve(url, filePath) 617 | if loader: 618 | logging.info('Loading %s...' % (name,)) 619 | loader(filePath) 620 | self.delayDisplay('Finished with download and loading') 621 | 622 | volumeNode = slicer.util.getNode(pattern="FA") 623 | logic = DistanceMapBasedRegistrationLogic() 624 | self.assertTrue( logic.hasImageData(volumeNode) ) 625 | self.delayDisplay('Test passed!') 626 | 627 | 628 | ''' 629 | 630 | TODO: 631 | * add main() so that registration could be run from command line 632 | 633 | ''' 634 | -------------------------------------------------------------------------------- /DistanceMapBasedRegistration/Resources/Icons/DistanceMapBasedRegistration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/DistanceMapBasedRegistration/Resources/Icons/DistanceMapBasedRegistration.png -------------------------------------------------------------------------------- /DistanceMapBasedRegistration/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Python) 2 | -------------------------------------------------------------------------------- /DistanceMapBasedRegistration/Testing/Python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) 3 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | 2 | For more information, please see: 3 | 4 | http://www.slicer.org 5 | 6 | The 3D Slicer license below is a BSD style license, with extensions 7 | to cover contributions and other issues specific to 3D Slicer. 8 | 9 | 10 | 3D Slicer Contribution and Software License Agreement ("Agreement") 11 | Version 1.0 (December 20, 2005) 12 | 13 | This Agreement covers contributions to and downloads from the 3D 14 | Slicer project ("Slicer") maintained by The Brigham and Women's 15 | Hospital, Inc. ("Brigham"). Part A of this Agreement applies to 16 | contributions of software and/or data to Slicer (including making 17 | revisions of or additions to code and/or data already in Slicer). Part 18 | B of this Agreement applies to downloads of software and/or data from 19 | Slicer. Part C of this Agreement applies to all transactions with 20 | Slicer. If you distribute Software (as defined below) downloaded from 21 | Slicer, all of the paragraphs of Part B of this Agreement must be 22 | included with and apply to such Software. 23 | 24 | Your contribution of software and/or data to Slicer (including prior 25 | to the date of the first publication of this Agreement, each a 26 | "Contribution") and/or downloading, copying, modifying, displaying, 27 | distributing or use of any software and/or data from Slicer 28 | (collectively, the "Software") constitutes acceptance of all of the 29 | terms and conditions of this Agreement. If you do not agree to such 30 | terms and conditions, you have no right to contribute your 31 | Contribution, or to download, copy, modify, display, distribute or use 32 | the Software. 33 | 34 | PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to 35 | Sublicense ("Contribution Agreement"). 36 | 37 | 1. As used in this Contribution Agreement, "you" means the individual 38 | contributing the Contribution to Slicer and the institution or 39 | entity which employs or is otherwise affiliated with such 40 | individual in connection with such Contribution. 41 | 42 | 2. This Contribution Agreement applies to all Contributions made to 43 | Slicer, including without limitation Contributions made prior to 44 | the date of first publication of this Agreement. If at any time you 45 | make a Contribution to Slicer, you represent that (i) you are 46 | legally authorized and entitled to make such Contribution and to 47 | grant all licenses granted in this Contribution Agreement with 48 | respect to such Contribution; (ii) if your Contribution includes 49 | any patient data, all such data is de-identified in accordance with 50 | U.S. confidentiality and security laws and requirements, including 51 | but not limited to the Health Insurance Portability and 52 | Accountability Act (HIPAA) and its regulations, and your disclosure 53 | of such data for the purposes contemplated by this Agreement is 54 | properly authorized and in compliance with all applicable laws and 55 | regulations; and (iii) you have preserved in the Contribution all 56 | applicable attributions, copyright notices and licenses for any 57 | third party software or data included in the Contribution. 58 | 59 | 3. Except for the licenses granted in this Agreement, you reserve all 60 | right, title and interest in your Contribution. 61 | 62 | 4. You hereby grant to Brigham, with the right to sublicense, a 63 | perpetual, worldwide, non-exclusive, no charge, royalty-free, 64 | irrevocable license to use, reproduce, make derivative works of, 65 | display and distribute the Contribution. If your Contribution is 66 | protected by patent, you hereby grant to Brigham, with the right to 67 | sublicense, a perpetual, worldwide, non-exclusive, no-charge, 68 | royalty-free, irrevocable license under your interest in patent 69 | rights covering the Contribution, to make, have made, use, sell and 70 | otherwise transfer your Contribution, alone or in combination with 71 | any other code. 72 | 73 | 5. You acknowledge and agree that Brigham may incorporate your 74 | Contribution into Slicer and may make Slicer available to members 75 | of the public on an open source basis under terms substantially in 76 | accordance with the Software License set forth in Part B of this 77 | Agreement. You further acknowledge and agree that Brigham shall 78 | have no liability arising in connection with claims resulting from 79 | your breach of any of the terms of this Agreement. 80 | 81 | 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION 82 | DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN 83 | SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting 84 | example, the GNU General Public License or other so-called 85 | "reciprocal" license that requires any derived work to be licensed 86 | under the GNU General Public License or other "open source 87 | license"). 88 | 89 | PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to 90 | Sublicense ("Software License"). 91 | 92 | 1. As used in this Software License, "you" means the individual 93 | downloading and/or using, reproducing, modifying, displaying and/or 94 | distributing the Software and the institution or entity which 95 | employs or is otherwise affiliated with such individual in 96 | connection therewith. The Brigham and Women?s Hospital, 97 | Inc. ("Brigham") hereby grants you, with right to sublicense, with 98 | respect to Brigham's rights in the software, and data, if any, 99 | which is the subject of this Software License (collectively, the 100 | "Software"), a royalty-free, non-exclusive license to use, 101 | reproduce, make derivative works of, display and distribute the 102 | Software, provided that: 103 | 104 | (a) you accept and adhere to all of the terms and conditions of this 105 | Software License; 106 | 107 | (b) in connection with any copy of or sublicense of all or any portion 108 | of the Software, all of the terms and conditions in this Software 109 | License shall appear in and shall apply to such copy and such 110 | sublicense, including without limitation all source and executable 111 | forms and on any user documentation, prefaced with the following 112 | words: "All or portions of this licensed product (such portions are 113 | the "Software") have been obtained under license from The Brigham and 114 | Women's Hospital, Inc. and are subject to the following terms and 115 | conditions:" 116 | 117 | (c) you preserve and maintain all applicable attributions, copyright 118 | notices and licenses included in or applicable to the Software; 119 | 120 | (d) modified versions of the Software must be clearly identified and 121 | marked as such, and must not be misrepresented as being the original 122 | Software; and 123 | 124 | (e) you consider making, but are under no obligation to make, the 125 | source code of any of your modifications to the Software freely 126 | available to others on an open source basis. 127 | 128 | 2. The license granted in this Software License includes without 129 | limitation the right to (i) incorporate the Software into 130 | proprietary programs (subject to any restrictions applicable to 131 | such programs), (ii) add your own copyright statement to your 132 | modifications of the Software, and (iii) provide additional or 133 | different license terms and conditions in your sublicenses of 134 | modifications of the Software; provided that in each case your use, 135 | reproduction or distribution of such modifications otherwise 136 | complies with the conditions stated in this Software License. 137 | 138 | 3. This Software License does not grant any rights with respect to 139 | third party software, except those rights that Brigham has been 140 | authorized by a third party to grant to you, and accordingly you 141 | are solely responsible for (i) obtaining any permissions from third 142 | parties that you need to use, reproduce, make derivative works of, 143 | display and distribute the Software, and (ii) informing your 144 | sublicensees, including without limitation your end-users, of their 145 | obligations to secure any such required permissions. 146 | 147 | 4. The Software has been designed for research purposes only and has 148 | not been reviewed or approved by the Food and Drug Administration 149 | or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL 150 | APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any 151 | commercialization of the Software is at the sole risk of the party 152 | or parties engaged in such commercialization. You further agree to 153 | use, reproduce, make derivative works of, display and distribute 154 | the Software in compliance with all applicable governmental laws, 155 | regulations and orders, including without limitation those relating 156 | to export and import control. 157 | 158 | 5. The Software is provided "AS IS" and neither Brigham nor any 159 | contributor to the software (each a "Contributor") shall have any 160 | obligation to provide maintenance, support, updates, enhancements 161 | or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY 162 | DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING, 163 | BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR 164 | A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 165 | BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT, 166 | INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES 167 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY 168 | RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS 169 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM 170 | EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL 171 | LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS, 172 | DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO 173 | INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND 174 | AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS 175 | ARISING THEREFROM. 176 | 177 | 6. None of the names, logos or trademarks of Brigham or any of 178 | Brigham's affiliates or any of the Contributors, or any funding 179 | agency, may be used to endorse or promote products produced in 180 | whole or in part by operation of the Software or derived from or 181 | based on the Software without specific prior written permission 182 | from the applicable party. 183 | 184 | 7. Any use, reproduction or distribution of the Software which is not 185 | in accordance with this Software License shall automatically revoke 186 | all rights granted to you under this Software License and render 187 | Paragraphs 1 and 2 of this Software License null and void. 188 | 189 | 8. This Software License does not grant any rights in or to any 190 | intellectual property owned by Brigham or any Contributor except 191 | those rights expressly granted hereunder. 192 | 193 | PART C. MISCELLANEOUS 194 | 195 | This Agreement shall be governed by and construed in accordance with 196 | the laws of The Commonwealth of Massachusetts without regard to 197 | principles of conflicts of law. This Agreement shall supercede and 198 | replace any license terms that you may have agreed to previously with 199 | respect to Slicer. 200 | -------------------------------------------------------------------------------- /Logo/SlicerProstate.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/Logo/SlicerProstate.ai -------------------------------------------------------------------------------- /QuadEdgeSurfaceMesher/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(MODULE_NAME QuadEdgeSurfaceMesher) 4 | 5 | #----------------------------------------------------------------------------- 6 | 7 | # 8 | # SlicerExecutionModel 9 | # 10 | find_package(SlicerExecutionModel REQUIRED) 11 | include(${SlicerExecutionModel_USE_FILE}) 12 | 13 | # 14 | # ITK 15 | # 16 | set(${PROJECT_NAME}_ITK_COMPONENTS 17 | ITKIOImageBase 18 | ITKSmoothing 19 | ITKQuadEdgeMesh 20 | ) 21 | find_package(ITK 4.6 COMPONENTS ${${PROJECT_NAME}_ITK_COMPONENTS} REQUIRED) 22 | set(ITK_NO_IO_FACTORY_REGISTER_MANAGER 1) # See Libs/ITKFactoryRegistration/CMakeLists.txt 23 | include(${ITK_USE_FILE}) 24 | 25 | #----------------------------------------------------------------------------- 26 | set(MODULE_INCLUDE_DIRECTORIES 27 | ) 28 | 29 | set(MODULE_SRCS 30 | ) 31 | 32 | set(MODULE_TARGET_LIBRARIES 33 | ${ITK_LIBRARIES} 34 | ${VTK_LIBRARIES} 35 | ) 36 | 37 | #----------------------------------------------------------------------------- 38 | SEMMacroBuildCLI( 39 | NAME ${MODULE_NAME} 40 | TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} 41 | INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} 42 | ADDITIONAL_SRCS ${MODULE_SRCS} 43 | ) 44 | 45 | #----------------------------------------------------------------------------- 46 | if(BUILD_TESTING) 47 | # add_subdirectory(Testing) 48 | endif() 49 | -------------------------------------------------------------------------------- /QuadEdgeSurfaceMesher/QuadEdgeSurfaceMesher.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "itkImageFileReader.h" 4 | #include "itkImageFileWriter.h" 5 | #include "itkQuadEdgeMesh.h" 6 | #include "itkTriangleCell.h" 7 | 8 | #include "itkBinaryThresholdImageFilter.h" 9 | #include "itkBinaryMask3DMeshSource.h" 10 | #include "itkQuadEdgeMeshDecimationCriteria.h" 11 | #include "itkSquaredEdgeLengthDecimationQuadEdgeMeshFilter.h" 12 | 13 | #include "itkMeshFileWriter.h" 14 | 15 | #include "vtkPLYWriter.h" 16 | #include "vtkSmartPointer.h" 17 | #include "vtkPolyData.h" 18 | 19 | #include "itkTriangleMeshToBinaryImageFilter.h" 20 | #include "itkImageDuplicator.h" 21 | 22 | #include 23 | 24 | const unsigned int Dimension = 3; 25 | typedef double PixelType; 26 | 27 | typedef itk::Image ImageType; // float to use uniform for all images 28 | typedef itk::ImageFileReader ReaderType; 29 | typedef itk::BinaryThresholdImageFilter ThreshType; 30 | 31 | typedef itk::QuadEdgeMesh < double,3 > MeshType; 32 | 33 | typedef MeshType::PointsContainer::Iterator PointsIterator; 34 | typedef MeshType::CellsContainer::Iterator CellsIterator; 35 | 36 | typedef itk::MeshFileWriter MeshWriterType; 37 | 38 | typedef itk::BinaryMask3DMeshSource< ImageType, MeshType > MeshSourceType; 39 | typedef itk::TriangleMeshToBinaryImageFilter Mesh2ImageType; 40 | 41 | vtkSmartPointer ITKMesh2PolyData(MeshType::Pointer); 42 | void PrintSurfaceStatistics(vtkPolyData*); 43 | void WriteMesh(MeshType::Pointer, const char*); 44 | void MeshLPStoRAS(MeshType::Pointer mesh); 45 | 46 | int main(int argc, char **argv){ 47 | PARSE_ARGS; 48 | 49 | ImageType::Pointer mask; 50 | 51 | ReaderType::Pointer reader = ReaderType::New(); 52 | reader->SetFileName(inputImageName.c_str()); 53 | 54 | ThreshType::Pointer thresh = ThreshType::New(); 55 | thresh->SetInput(reader->GetOutput()); 56 | thresh->SetLowerThreshold(labelId); 57 | thresh->SetUpperThreshold(labelId); 58 | thresh->SetInsideValue(1); 59 | thresh->Update(); 60 | 61 | MeshSourceType::Pointer meshSource = MeshSourceType::New(); 62 | 63 | meshSource->SetInput( thresh->GetOutput()); 64 | meshSource->SetObjectValue(1); 65 | meshSource->Update(); 66 | 67 | std::cout << "MC surface points: " << meshSource->GetNumberOfNodes() << std::endl; 68 | std::cout << "MC surface cells: " << meshSource->GetNumberOfCells() << std::endl; 69 | 70 | // decimate the mesh 71 | typedef itk::NumberOfFacesCriterion< MeshType > CriterionType; 72 | typedef itk::SquaredEdgeLengthDecimationQuadEdgeMeshFilter< 73 | MeshType, MeshType, CriterionType > DecimationType; 74 | 75 | CriterionType::Pointer criterion = CriterionType::New(); 76 | criterion->SetTopologicalChange( false ); 77 | std::cout << "Target number of cells after decimation: " << 78 | (unsigned) (decimationConst*meshSource->GetNumberOfCells()) << std::endl; 79 | criterion->SetNumberOfElements( unsigned(decimationConst*meshSource->GetNumberOfCells())); 80 | 81 | MeshType::Pointer mcmesh = meshSource->GetOutput(); 82 | 83 | DecimationType::Pointer decimate = DecimationType::New(); 84 | decimate->SetInput( meshSource->GetOutput() ); 85 | decimate->SetCriterion( criterion ); 86 | decimate->Update(); 87 | 88 | MeshType::Pointer dMesh = decimate->GetOutput(); 89 | 90 | MeshLPStoRAS(dMesh); 91 | 92 | std::cout << "Decimation complete" << std::endl; 93 | std::cout << "Decimated surface points: " << dMesh->GetPoints()->Size() << std::endl; 94 | std::cout << "Decimated surface cells: " << dMesh->GetCells()->Size() << std::endl; 95 | WriteMesh(dMesh, outputMeshName.c_str()); 96 | 97 | return EXIT_SUCCESS; 98 | 99 | } 100 | 101 | 102 | void MeshLPStoRAS(MeshType::Pointer mesh){ 103 | PointsIterator pIt = mesh->GetPoints()->Begin(); 104 | PointsIterator pEnd = mesh->GetPoints()->End(); 105 | unsigned i = 0; 106 | 107 | while(pIt!=pEnd){ 108 | MeshType::PointType p = pIt.Value(); 109 | p[0] *= -1.; 110 | p[1] *= -1; 111 | mesh->SetPoint(i, p); 112 | pIt++; 113 | i++; 114 | } 115 | } 116 | 117 | vtkSmartPointer ITKMesh2PolyData(MeshType::Pointer mesh){ 118 | vtkSmartPointer surface = vtkSmartPointer::New(); 119 | vtkSmartPointer surfacePoints = vtkSmartPointer::New(); 120 | 121 | surfacePoints->SetNumberOfPoints(mesh->GetPoints()->Size()); 122 | 123 | PointsIterator pIt = mesh->GetPoints()->Begin(), pItEnd = mesh->GetPoints()->End(); 124 | CellsIterator cIt = mesh->GetCells()->Begin(), cItEnd = mesh->GetCells()->End(); 125 | 126 | while(pIt!=pItEnd){ 127 | MeshType::PointType pt = pIt->Value(); 128 | surfacePoints->SetPoint(pIt->Index(), pt[0], pt[1], pt[2]); 129 | ++pIt; 130 | } 131 | 132 | surface->SetPoints(surfacePoints); 133 | surface->Allocate(); 134 | 135 | while(cIt!=cItEnd){ 136 | MeshType::CellType *cell = cIt->Value(); 137 | MeshType::CellType::PointIdIterator pidIt = cell->PointIdsBegin(); 138 | vtkIdType cIds[3]; 139 | cIds[0] = *pidIt; 140 | cIds[1] = *(pidIt+1); 141 | cIds[2] = *(pidIt+2); 142 | surface->InsertNextCell(VTK_TRIANGLE, 3, cIds); 143 | 144 | ++cIt; 145 | } 146 | return surface; 147 | } 148 | 149 | void WriteMesh(MeshType::Pointer mesh, const char* fname){ 150 | vtkSmartPointer vtksurf = ITKMesh2PolyData(mesh); 151 | vtkSmartPointer pdw = vtkSmartPointer::New(); 152 | pdw->SetFileName(fname); 153 | pdw->SetInputData(vtksurf); 154 | pdw->Update(); 155 | } 156 | -------------------------------------------------------------------------------- /QuadEdgeSurfaceMesher/QuadEdgeSurfaceMesher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Converters 4 | QuadEdge Surface Mesher 5 | 6 | 0.0.1 7 | http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/QuadEdgeSurfaceMesher 8 | Slicer 9 | Andrey Fedorov (SPL/BWH) 10 | This work supported in part the National Institutes of Health, National Cancer Institute through the following grants: Enabling technologies for MRI-guided prostate interventions (R01 CA111288, PI Tempany), The National Center for Image-Guided Therapy (P41 EB015898, PI Tempany) Quantitative Image Informatics for Cancer Research (QIICR) (U24 CA180918, PIs Kikinis and Fedorov) 11 | 12 | 13 | Input/output parameters 14 | 15 | 16 | inputImageName 17 | 18 | input 19 | Segmentation label image 20 | 0 21 | 22 | 23 | 24 | labelId 25 | 26 | label 27 | input 28 | Label to mesh. By default, segmentation corresponding to label 1 will be meshed. 29 | 1 30 | 31 | 32 | 33 | decimationConst 34 | 35 | decimation 36 | input 37 | Number of cells in the output mesh will be the original number of cells times the decimation constant 38 | 0.1 39 | 40 | 41 | 42 | outputMeshName 43 | 44 | output 45 | Surface 46 | 1 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlicerProstate 2 | 3 | ## Introduction 4 | 5 | SlicerProstate is an extension of 3D Slicer software (http://slicer.org) that provides a collection of modules to facilitate 6 | * processing and management of prostate image data 7 | * utilizing prostate images in image-guided interventions 8 | * development of the imaging biomarkers of the prostate cancer 9 | 10 | While the main motivation for developing the functionality contained in this extension was prostate cancer imaging applications, they can also be applied in different contexts. 11 | 12 | See documentation at 13 | http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Extensions/SlicerProstate. 14 | 15 | ## Functionality 16 | 17 | Current modules include: 18 | * [Distance Map Based Registration](http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/DistanceMapBasedRegistration): segmentation-based registration approach that can be used to support fusion of MRI-TRUS images 19 | * [Segmentation 20 | Smoothing](http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/SegmentationSmoothing): 21 | utility to smooth segmentations done on thick-slice images 22 | * [Quad Edge Surface 23 | Mesher](http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/QuadEdgeSurfaceMesher): 24 | utility to decimate and smooth triangular surface meshes 25 | * [DWModeling](http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/DWModeling): 26 | fitting of the various models to Diffusion Weighted MRI trace images 27 | 28 | 29 | This extension is work in progress, and we are planning to add other modules relevant to prostate cancer imaging research .... stay tuned. 30 | 31 | 32 | ## Acknowledgments 33 | 34 | This work supported in part the National Institutes of Health, National Cancer Institute through the following grants: 35 | * Quantitative MRI of prostate cancer as a biomarker and guide for treatment, Quantitative Imaging Network (U01 CA151261, PI Fennessy) 36 | * Enabling technologies for MRI-guided prostate interventions (R01 CA111288, PI Tempany) 37 | * The National Center for Image-Guided Therapy (P41 EB015898, PI Tempany) 38 | * Advancement and Validation of Prostate Diffusion and Spectroscopic MRI (R01 CA160902, PI Maier) 39 | * Quantitative Image Informatics for Cancer Research (QIICR) (U24 CA180918, PIs Kikinis and Fedorov) 40 | 41 | The following individuals and groups contributed directly to the development of SlicerProstate functionality: 42 | * Andrey Fedorov, Brigham and Women's Hospital 43 | * Andras Lasso, Queen's University 44 | * Alireza Mehrtash, Brigham and Women's Hospital 45 | 46 | ## References 47 | 48 | If you use SlicerProstate in your work, we would be grateful if you could acknowledge it by citing 49 | the following publication. You can also use it to learn more about SlicerProstate functionality! 50 | 51 | > Fedorov, A., Khallaghi, S., Sánchez, C. A., Lasso, A., Fels, S., Tuncali, K., Sugar, E. N., Kapur, T., Zhang, C., Wells, W., Nguyen, P. L., Abolmaesumi, P. & Tempany, C. Open-source image registration for MRI-TRUS fusion-guided prostate interventions. Int. J. Comput. Assist. Radiol. Surg. 10, 925–934 (2015). Available: https://pubmed.ncbi.nlm.nih.gov/25847666/ 52 | 53 | A sample dataset you can use for testing is available here. If you use this dataset, please cite it! 54 | 55 | > Fedorov A, Nguyen PL, Tuncali K, Tempany C. (2015). Annotated MRI and ultrasound volume images of the prostate. Zenodo. http://doi.org/10.5281/zenodo.16396 56 | 57 | ## Contact 58 | 59 | If you need more information, would like to contribute relevant functionality or improvement, or have suggestions for the future development please contact 60 | 61 | Andrey Fedorov fedorov@bwh.harvard.edu 62 | -------------------------------------------------------------------------------- /SegmentationSmoothing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(MODULE_NAME SegmentationSmoothing) 4 | 5 | #----------------------------------------------------------------------------- 6 | 7 | # 8 | # SlicerExecutionModel 9 | # 10 | find_package(SlicerExecutionModel REQUIRED) 11 | include(${SlicerExecutionModel_USE_FILE}) 12 | 13 | # 14 | # ITK 15 | # 16 | set(${PROJECT_NAME}_ITK_COMPONENTS 17 | ITKIOImageBase 18 | ITKSmoothing 19 | ) 20 | find_package(ITK 4.6 COMPONENTS ${${PROJECT_NAME}_ITK_COMPONENTS} REQUIRED) 21 | set(ITK_NO_IO_FACTORY_REGISTER_MANAGER 1) # See Libs/ITKFactoryRegistration/CMakeLists.txt 22 | include(${ITK_USE_FILE}) 23 | 24 | #----------------------------------------------------------------------------- 25 | set(MODULE_INCLUDE_DIRECTORIES 26 | ) 27 | 28 | set(MODULE_SRCS 29 | ) 30 | 31 | set(MODULE_TARGET_LIBRARIES 32 | ${ITK_LIBRARIES} 33 | ) 34 | 35 | #----------------------------------------------------------------------------- 36 | SEMMacroBuildCLI( 37 | NAME ${MODULE_NAME} 38 | TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} 39 | INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} 40 | ADDITIONAL_SRCS ${MODULE_SRCS} 41 | ) 42 | 43 | #----------------------------------------------------------------------------- 44 | if(BUILD_TESTING) 45 | # add_subdirectory(Testing) 46 | endif() 47 | -------------------------------------------------------------------------------- /SegmentationSmoothing/SegmentationSmoothing.cxx: -------------------------------------------------------------------------------- 1 | #include // for std::min and std::max 2 | 3 | #include "itkSmoothingRecursiveGaussianImageFilter.h" 4 | #include "itkBinaryThresholdImageFilter.h" 5 | #include "itkImageFileWriter.h" 6 | #include "itkImageFileReader.h" 7 | #include "itkResampleImageFilter.h" 8 | #include "itkNearestNeighborInterpolateImageFunction.h" 9 | 10 | #include "SegmentationSmoothingCLP.h" 11 | 12 | int main( int argc, char * argv[] ) 13 | { 14 | PARSE_ARGS; 15 | 16 | const unsigned int Dimension = 3; 17 | 18 | typedef itk::Image ImageType; 19 | typedef itk::Image FloatImageType; 20 | 21 | typedef itk::ImageFileReader ReaderType; 22 | typedef itk::SmoothingRecursiveGaussianImageFilter SmootherType; 23 | typedef itk::ImageFileWriter WriterType; 24 | typedef itk::BinaryThresholdImageFilter LabelThreshType; 25 | typedef itk::BinaryThresholdImageFilter SmoothedThreshType; 26 | typedef itk::ResampleImageFilter ResamplerType; 27 | typedef itk::NearestNeighborInterpolateImageFunction InterpolatorType; 28 | 29 | ReaderType::Pointer reader = ReaderType::New(); 30 | reader->SetFileName(inputImageName.c_str()); 31 | reader->Update(); 32 | 33 | ImageType::Pointer inputImage = reader->GetOutput(); 34 | 35 | ImageType::SpacingType inputSpacing = inputImage->GetSpacing(); 36 | ImageType::SpacingType outputSpacing, smoothSpacing; 37 | float minSpacing = std::min(std::min(inputSpacing[0],inputSpacing[1]),inputSpacing[2]); 38 | float maxSpacing = std::max(std::max(inputSpacing[0],inputSpacing[1]),inputSpacing[2]); 39 | outputSpacing[0] = minSpacing; 40 | outputSpacing[1] = minSpacing; 41 | outputSpacing[2] = minSpacing; 42 | smoothSpacing[0] = maxSpacing; 43 | smoothSpacing[1] = maxSpacing; 44 | smoothSpacing[2] = maxSpacing; 45 | 46 | ImageType::SizeType outputSize, inputSize; 47 | inputSize = inputImage->GetLargestPossibleRegion().GetSize(); 48 | typedef ImageType::SizeType::SizeValueType SizeValueType; 49 | outputSize[0] = static_cast(inputSize[0]*inputSpacing[0]/outputSpacing[0] + .5); 50 | outputSize[1] = static_cast(inputSize[1]*inputSpacing[1]/outputSpacing[0] + .5); 51 | outputSize[2] = static_cast(inputSize[2]*inputSpacing[2]/outputSpacing[0] + .5); 52 | 53 | typedef itk::IdentityTransform TransformType; 54 | TransformType::Pointer eye = TransformType::New(); 55 | eye->SetIdentity(); 56 | 57 | InterpolatorType::Pointer interp = InterpolatorType::New(); 58 | ResamplerType::Pointer resampler = ResamplerType::New(); 59 | resampler->SetOutputSpacing(outputSpacing); 60 | resampler->SetOutputDirection(inputImage->GetDirection()); 61 | resampler->SetOutputOrigin(inputImage->GetOrigin()); 62 | resampler->UseReferenceImageOff(); 63 | resampler->SetInterpolator(interp); 64 | resampler->SetSize(outputSize); 65 | resampler->SetTransform(eye); 66 | resampler->SetInput(inputImage); 67 | try{ 68 | resampler->Update(); 69 | } catch(itk::ExceptionObject &e){ 70 | std::cout << "Resampling failed" << std::endl; 71 | } 72 | if(0){ 73 | typedef itk::ImageFileWriter WriterType; 74 | WriterType::Pointer imageWriter = WriterType::New(); 75 | imageWriter->SetInput(resampler->GetOutput() ); 76 | imageWriter->SetFileName( outputImageName.c_str() ); 77 | imageWriter->UseCompressionOn(); 78 | imageWriter->Update(); 79 | } 80 | 81 | // convert to label 1 first, then smooth, then threshold at 0.5 82 | LabelThreshType::Pointer labelThresh = LabelThreshType::New(); 83 | labelThresh->SetInput(resampler->GetOutput()); 84 | labelThresh->SetInsideValue(1); 85 | if(labelNumber==-1){ 86 | labelThresh->SetUpperThreshold(255); 87 | labelThresh->SetLowerThreshold(1); 88 | } else { 89 | labelThresh->SetUpperThreshold(labelNumber); 90 | labelThresh->SetLowerThreshold(labelNumber); 91 | } 92 | 93 | try{ 94 | labelThresh->Update(); 95 | } catch(itk::ExceptionObject &e){ 96 | std::cout << "label threshold failed" << std::endl; 97 | } 98 | 99 | SmootherType::Pointer smoother = SmootherType::New(); 100 | smoother->SetInput(labelThresh->GetOutput()); 101 | smoother->SetSigmaArray(smoothSpacing); 102 | std::cout << " Sigma : " << inputSpacing <Update(); 105 | } catch(itk::ExceptionObject &e){ 106 | std::cout << "smoothing failed" << std::endl; 107 | } 108 | 109 | SmoothedThreshType::Pointer smoothThresh = SmoothedThreshType::New(); 110 | smoothThresh->SetInput(smoother->GetOutput()); 111 | smoothThresh->SetInsideValue(1); 112 | smoothThresh->SetUpperThreshold(255); 113 | smoothThresh->SetLowerThreshold(0.5); 114 | try{ 115 | smoothThresh->Update(); 116 | } catch(itk::ExceptionObject &e){ 117 | std::cout << "smooth thresh failed" << std::endl; 118 | } 119 | 120 | { 121 | typedef itk::ImageFileWriter WriterType; 122 | WriterType::Pointer imageWriter = WriterType::New(); 123 | imageWriter->SetInput(smoothThresh->GetOutput() ); 124 | imageWriter->SetFileName( outputImageName.c_str() ); 125 | imageWriter->UseCompressionOn(); 126 | imageWriter->Update(); 127 | } 128 | 129 | return EXIT_SUCCESS; 130 | } 131 | -------------------------------------------------------------------------------- /SegmentationSmoothing/SegmentationSmoothing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Filtering 4 | Segmentation Smoothing 5 | 6 | 0.0.1 7 | http://wiki.slicer.org/slicerWiki/index.php/Documentation/Nightly/Modules/DistanceMapBasedRegistration 8 | Slicer 9 | Andrey Fedorov (BWH) 10 | This work supported in part the National Institutes of Health, National Cancer Institute through the following grants: Enabling technologies for MRI-guided prostate interventions (R01 CA111288, PI Tempany), The National Center for Image-Guided Therapy (P41 EB015898, PI Tempany) Quantitative Image Informatics for Cancer Research (QIICR) (U24 CA180918, PIs Kikinis and Fedorov) 11 | 12 | 13 | 14 | 15 | 16 | inputImageName 17 | 18 | input 19 | 0 20 | 21 | 22 | 23 | 24 | outputImageName 25 | 26 | output 27 | 1 28 | 29 | 30 | 31 | 32 | labelNumber 33 | 34 | input 35 | label 36 | l 37 | -1 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SegmentationSmoothing/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Cxx) 2 | -------------------------------------------------------------------------------- /SegmentationSmoothing/Testing/Cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(BASELINE ${CMAKE_CURRENT_SOURCE_DIR}/../../Data/Baseline) 4 | set(INPUT ${CMAKE_CURRENT_SOURCE_DIR}/../../Data/Input) 5 | set(TEMP "${CMAKE_BINARY_DIR}/Testing/Temporary") 6 | 7 | set(CLP ${MODULE_NAME}) 8 | 9 | #----------------------------------------------------------------------------- 10 | add_executable(${CLP}Test ${CLP}Test.cxx) 11 | target_link_libraries(${CLP}Test ${CLP}Lib ${SlicerExecutionModel_EXTRA_EXECUTABLE_TARGET_LIBRARIES}) 12 | set_target_properties(${CLP}Test PROPERTIES LABELS ${CLP}) 13 | 14 | #----------------------------------------------------------------------------- 15 | set(testname ${CLP}Test) 16 | ExternalData_add_test(${CLP}Data NAME ${testname} COMMAND ${SEM_LAUNCH_COMMAND} $ 17 | --compare DATA{${BASELINE}/${CLP}Test.nhdr,${CLP}Test.raw} 18 | ${TEMP}/${CLP}Test.nhdr 19 | ModuleEntryPoint 20 | --sigma 2.5 DATA{${INPUT}/CTHeadAxial.nhdr,CTHeadAxial.raw.gz} ${TEMP}/${CLP}Test.nhdr 21 | ) 22 | set_property(TEST ${testname} PROPERTY LABELS ${CLP}) 23 | 24 | #----------------------------------------------------------------------------- 25 | ExternalData_add_target(${CLP}Data) 26 | -------------------------------------------------------------------------------- /SegmentationSmoothing/Testing/Cxx/SegmentationSmoothingTest.cxx: -------------------------------------------------------------------------------- 1 | #if defined(_MSC_VER) 2 | #pragma warning ( disable : 4786 ) 3 | #endif 4 | 5 | #ifdef __BORLANDC__ 6 | #define ITK_LEAN_AND_MEAN 7 | #endif 8 | 9 | #include "itkTestMain.h" 10 | 11 | // STD includes 12 | #include 13 | 14 | #ifdef WIN32 15 | # define MODULE_IMPORT __declspec(dllimport) 16 | #else 17 | # define MODULE_IMPORT 18 | #endif 19 | 20 | extern "C" MODULE_IMPORT int ModuleEntryPoint(int, char* []); 21 | 22 | void RegisterTests() 23 | { 24 | StringToTestFunctionMap["ModuleEntryPoint"] = ModuleEntryPoint; 25 | } 26 | -------------------------------------------------------------------------------- /SlicerProstate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlicerProstate/SlicerProstate/70068ea14b8d87ab1f4040047a584a15a76fd94a/SlicerProstate.png --------------------------------------------------------------------------------