diff --git a/Modules/Radiomics/RadiomicsCore/src/mitkRadiomicsSegmentationTools.cpp b/Modules/Radiomics/RadiomicsCore/src/mitkRadiomicsSegmentationTools.cpp index 4311f2da97..f98bcd52dd 100644 --- a/Modules/Radiomics/RadiomicsCore/src/mitkRadiomicsSegmentationTools.cpp +++ b/Modules/Radiomics/RadiomicsCore/src/mitkRadiomicsSegmentationTools.cpp @@ -1,308 +1,304 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include //ITK includes #include #include #include #include #include #include #include "mitkPixelTypeMultiplex.h" #include bool mitk::SegmentationTools::isCorrectionNeeded(mitk::Image::Pointer inputImage, mitk::Image::Pointer maskImage) { /// /// Check if we need to transform the mask /// auto imageGeom = inputImage->GetGeometry(); auto maskGeom = maskImage->GetGeometry(); if(inputImage->GetDimensions()[0] == maskImage->GetDimensions()[0] && inputImage->GetDimensions()[1] == maskImage->GetDimensions()[1] && inputImage->GetDimensions()[2] == maskImage->GetDimensions()[2] && imageGeom->GetSpacing() == maskGeom->GetSpacing() && imageGeom->GetOrigin() == imageGeom->GetOrigin() && imageGeom->GetIndexToWorldTransform()->GetTranslation() == maskGeom->GetIndexToWorldTransform()->GetTranslation() && imageGeom->GetIndexToWorldTransform()->GetOffset() == maskGeom->GetIndexToWorldTransform()->GetOffset() && imageGeom->GetIndexToWorldTransform()->GetMatrix() == maskGeom->GetIndexToWorldTransform()->GetMatrix()) { return false; } return true; } mitk::Image::Pointer mitk::SegmentationTools::makeRoiFitToImage(mitk::Image::Pointer inputImage, mitk::Image::Pointer maskImage) { /// /// Load segmentation and transform it to a surface /// We don't check if we really need a transformation here, /// this can be done by calling isCorrectionNeeded(*,*) /// bool smooth = false; double gaussianSD = 1.5; bool applyMedian = false; double medianKernelSize = 3; bool decimateMesh = false; double reductionRate = 0.5; mitk::ManualSegmentationToSurfaceFilter::Pointer surfaceFilter = mitk::ManualSegmentationToSurfaceFilter::New(); surfaceFilter->SetInput(maskImage); surfaceFilter->SetThreshold(0.5); //expects binary image with zeros and ones surfaceFilter->SetUseGaussianImageSmooth(smooth); // apply gaussian to thresholded image ? surfaceFilter->SetSmooth(smooth); if (smooth) { surfaceFilter->InterpolationOn(); surfaceFilter->SetGaussianStandardDeviation(gaussianSD); } surfaceFilter->SetMedianFilter3D(applyMedian); // apply median to segmentation before marching cubes ? if (applyMedian) { surfaceFilter->SetMedianKernelSize(medianKernelSize, medianKernelSize, medianKernelSize); // apply median to segmentation before marching cubes } //fix to avoid vtk warnings see bug #5390 if (maskImage->GetDimension() > 3) decimateMesh = false; if (decimateMesh) { surfaceFilter->SetDecimate(mitk::ImageToSurfaceFilter::QuadricDecimation); surfaceFilter->SetTargetReduction(reductionRate); } else { surfaceFilter->SetDecimate(mitk::ImageToSurfaceFilter::NoDecimation); } surfaceFilter->UpdateLargestPossibleRegion(); // calculate normals for nicer display mitk::Surface::Pointer mySurface = surfaceFilter->GetOutput(); mitk::Image::Pointer resultImage(nullptr); /// /// Transform surface back to image /// mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(mySurface); surfaceToImageFilter->SetImage(inputImage); - - MITK_WARN << "Size of inputImage: " << inputImage->GetDimensions(); - - + try { surfaceToImageFilter->Update(); } catch (itk::ExceptionObject& excpt) { MITK_ERROR << excpt.GetDescription(); return maskImage; } resultImage = surfaceToImageFilter->GetOutput(); - MITK_WARN << "Size of resultImage: " << resultImage->GetDimensions(); if (resultImage.IsNull()) { MITK_ERROR << "Convert Surface to binary image failed"; return maskImage; } return resultImage; } /// /// \brief mitk::SegmentationTools::shrinkImageToLevel /// \param inputImage - the image that should be shrinked in X and Y direction (Z direction will be kept) /// \param level - unsigned int level of shrinkage (usually 2,4,8,16,...) /// \return the shrinked image mitk::Image::Pointer mitk::SegmentationTools::shrinkImageToLevel(Image::Pointer inputImage, unsigned int level) { if (level > 1) { mitk::Image::Pointer ritw = mitk::Image::New(); ritw = mitk::GeometryTools::ResetIndexToWorld(inputImage); const unsigned int Dimension = 3; typedef unsigned short PixelType; typedef itk::Image< PixelType, Dimension > MaskImageType; typedef itk::Image< PixelType, Dimension > ImageType; MaskImageType::Pointer itkMaskImage = NULL; mitk::Image::Pointer resultImage; mitk::CastToItkImage(ritw, itkMaskImage); // Resize MITK_WARN << "current Size " << itkMaskImage->GetLargestPossibleRegion().GetSize()[0] << " " << itkMaskImage->GetLargestPossibleRegion().GetSize()[1] << " " << itkMaskImage->GetLargestPossibleRegion().GetSize()[2]; MITK_WARN << "Shrink Factor " << level; ImageType::SizeType outputSize; outputSize[0] = itkMaskImage->GetLargestPossibleRegion().GetSize()[0] / level; outputSize[1] = itkMaskImage->GetLargestPossibleRegion().GetSize()[1] / level; outputSize[2] = itkMaskImage->GetLargestPossibleRegion().GetSize()[2]; ImageType::SpacingType outputSpacing; outputSpacing[0] = itkMaskImage->GetSpacing()[0] * level; outputSpacing[1] = itkMaskImage->GetSpacing()[1] * level; outputSpacing[2] = itkMaskImage->GetSpacing()[2]; typedef itk::ScalableAffineTransform TransformType; typedef itk::ResampleImageFilter ResampleImageFilterType; ResampleImageFilterType::Pointer filter = ResampleImageFilterType::New(); filter->SetInput(itkMaskImage); filter->SetSize(outputSize); filter->SetOutputSpacing(outputSpacing); filter->SetTransform(TransformType::New()); filter->Update(); filter->UpdateLargestPossibleRegion(); typedef itk::ChangeInformationImageFilter< ImageType > FilterType; FilterType::Pointer changeSpacing = FilterType::New(); changeSpacing->SetInput(filter->GetOutput()); ImageType::SpacingType spacing; spacing[0] = itkMaskImage->GetSpacing()[0]; spacing[1] = itkMaskImage->GetSpacing()[1]; spacing[2] = itkMaskImage->GetSpacing()[2]; changeSpacing->SetOutputSpacing(spacing); changeSpacing->ChangeSpacingOn(); try { changeSpacing->UpdateOutputInformation(); } catch (itk::ExceptionObject & error) { std::cerr << "Error: " << error << std::endl; return ritw; } try { mitk::CastToMitkImage(filter->GetOutput(), resultImage); } catch (itk::ExceptionObject & error) { MITK_WARN << "There was an error while casting to MITK Image."; return ritw; } auto newGeom = resultImage->GetGeometry(); auto newI2W = newGeom->GetIndexToWorldTransform(); auto newI2WMatrix = newI2W->GetMatrix(); newI2WMatrix[0][0] =1; newI2WMatrix[1][1] =1; newI2WMatrix[2][2] =1; newI2W->SetMatrix(newI2WMatrix); newGeom->SetIndexToWorldTransform(newI2W); resultImage->SetGeometry(newGeom); return resultImage; } } mitk::Image::Pointer mitk::SegmentationTools::removeNaNsFromSegmentation(Image::Pointer inputImage, Image::Pointer maskImage) { const unsigned int Dimension = 3; typedef unsigned short PixelType; typedef itk::Image< PixelType, Dimension > ImageType; ImageType::Pointer itkMask = ImageType::New(); mitk::CastToItkImage(maskImage, itkMask); ImageType::Pointer resultImage; mitk::CastToItkImage(maskImage, resultImage); int xx = itkMask->GetLargestPossibleRegion().GetSize()[0]; int yy = itkMask->GetLargestPossibleRegion().GetSize()[1]; int zz = itkMask->GetLargestPossibleRegion().GetSize()[2]; //MITK_WARN << xx << " " << yy << " " << zz; for (int x = 0; x < xx; x++) { for (int y = 0; y < yy; y++) { for (int z = 0; z < zz; z++) { //MITK_WARN << "Checking Pos " << x << " " << y << " " << z; ImageType::IndexType index; index[0] = x; index[1] = y; index[2] = z; mitk::ScalarType pxImage; mitkPixelTypeMultiplex5( mitk::FastSinglePixelAccess, inputImage->GetChannelDescriptor().GetPixelType(), inputImage, inputImage->GetVolumeData(), index, pxImage, 0); unsigned short pxMask = itkMask->GetPixel(index); //MITK_WARN << " -> Image/Segmentation Value: " << pxImage << "/" << pxMask; //Check for NaNs if (pxImage != pxImage) //if pxImage is NaN, then f!=f! { resultImage->SetPixel(index, 0); } else { resultImage->SetPixel(index, pxMask); } } } } mitk::Image::Pointer resultImageMitk; mitk::CastToMitkImage(resultImage,resultImageMitk); return resultImageMitk; } diff --git a/Modules/Radiomics/RadiomicsMiniApps/RadiomicsPipeline.cpp b/Modules/Radiomics/RadiomicsMiniApps/RadiomicsPipeline.cpp index 9e89dc7e4b..3d907043e2 100644 --- a/Modules/Radiomics/RadiomicsMiniApps/RadiomicsPipeline.cpp +++ b/Modules/Radiomics/RadiomicsMiniApps/RadiomicsPipeline.cpp @@ -1,542 +1,541 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkCommandLineParser.h" #include "mitkImage.h" #include "mitkIOUtil.h" #include #include #include #include "mitkIOUtil.h" #include #include //MITK #include #include #include #include #include #include //Used to check if file exists before trying to open it. //However, this might get a wrong result if the file exists but the user is not allowed to open it. bool fileExists(const std::string& filename) { ifstream infile(filename.c_str()); return infile.good(); } void radiomicsPipeline(std::string inputPath, std::string maskPath, std::string outputPath, bool useNorm, bool useGeom, bool useRes, float resX, float resY, float resZ , bool useDaub, bool useCoif, int wLevels, bool useStationary, bool firstOF, bool volumeF, bool textureF, bool calc2D, std::string coocString, std::string textureBins ) { if(!fileExists(inputPath) || !fileExists(maskPath)) { MITK_ERROR << "Not allimages available!"; return; } mitk::Image::Pointer image = mitk::IOUtil::LoadImage(inputPath); mitk::Image::Pointer segmentation = mitk::IOUtil::LoadImage(maskPath); std::string imageName = itksys::SystemTools::GetFilenameName(inputPath); std::string maskName = itksys::SystemTools::GetFilenameName(maskPath); std::string folderName = itksys::SystemTools::GetFilenamePath(inputPath); /// /// Image Correction /// if(useNorm) { MITK_WARN << "Perform Image Correction..."; image = mitk::RadiomicsNormalization::normalizeImage(image); } /// /// Resampling /// if(useRes) { MITK_WARN << "Perform Resampling..."; - mitk::RadiomicsResampling::Pointer myRes = mitk::RadiomicsResampling::New(); image = mitk::RadiomicsResampling::resampleImageFloat(image, resX, resY, calc2D ? (float)image->GetGeometry()->GetSpacing()[2]: (float)resZ); //resample segmentation image segmentation = mitk::RadiomicsResampling::resampleMaskUnsignedShort(segmentation, resX, resY, calc2D ? (float)image->GetGeometry()->GetSpacing()[2]: (float)resZ); } /// /// Make the segmentation fit to the image (w.r.t geometry etc.) /// if(mitk::SegmentationTools::isCorrectionNeeded(image,segmentation)) { MITK_WARN << "Making ROI fit to image"; segmentation = mitk::SegmentationTools::makeRoiFitToImage(image,segmentation); } /// /// Geometry correction /// if(useGeom) { MITK_WARN << "checking Geometry!"; //adjust image MITK_WARN << "Initial New image dimensions: " << image->GetDimensions()[0]; image = mitk::GeometryTools::ResetIndexToWorld(image); //adjust segmentation segmentation = mitk::GeometryTools::ResetIndexToWorld(segmentation); } /// /// Remove NaNs from Segmentation image to prevent miscalculated features. /// /// bool removeNaNs = false; if(removeNaNs) { segmentation = mitk::SegmentationTools::removeNaNsFromSegmentation(image,segmentation->Clone()); } /// /// Calculate the features /// if(firstOF == false && volumeF == false && textureF == false) { // } else { MITK_WARN << "Calculating features."; mitk::AbstractGlobalImageFeature::FeatureListType stats = mitk::RadiomicsFeatures::calculateFeatures(image, segmentation, coocString, textureBins, (calc2D ? 4 : 0), firstOF, volumeF, textureF); mitk::RadiomicsFeatures::writeToCsvFile(stats,outputPath,folderName, imageName, maskName); } /// /// Calculate Coiflet Wavelets /// if(useCoif) { mitk::RadiomicsWavelets::Pointer wavelets = mitk::RadiomicsWavelets::New(); wavelets->setup(); std::vector wavs = wavelets->createCoifletWavelets(image, wLevels,useStationary); std::vector finalSegs; //Contains the fitted and shrinked segs std::vector finalWavs; //Contains the wavelets in the same order as the finalSegs if(wavs.size() == 0) { MITK_ERROR << "Wavelet calculation failed. Aborting."; return; } else { for(int i = wLevels; i > 0; i--) { mitk::DataNode::Pointer tmpWavNode = mitk::DataNode::New(); mitk::Image::Pointer empty = mitk::Image::New(); empty->Initialize(image); tmpWavNode->SetData(empty); tmpWavNode->SetVisibility(false); tmpWavNode->SetName("Coiflet Level " + std::to_string(wLevels - i + 1 )); for(int j = 0; j < 4; j++) { int index = (i-1) * 4 + j; //Let's fit the segmentation to the wavelet image mitk::Image::Pointer finalSeg = mitk::SegmentationTools::makeRoiFitToImage(wavs[index],segmentation->Clone()); finalSegs.push_back(finalSeg); finalWavs.push_back(wavs[index]); std::string waveletType = "Lowpass"; if(j == 3) waveletType = "H"; else if(j == 2) waveletType = "V"; else if(j == 1) waveletType = "D"; mitk::DataNode::Pointer tmpImageNode = mitk::DataNode::New(); tmpImageNode->SetData(wavs[index]); tmpImageNode->SetName("Image " + waveletType); tmpImageNode->SetVisibility(false); mitk::DataNode::Pointer tmpSegNode = mitk::DataNode::New(); tmpSegNode->SetData(finalSeg); //tmpSegNode->SetData(segs[index]); tmpSegNode->SetName("Segmentation " + waveletType); tmpSegNode->SetVisibility(false); } } } /// /// Calculate the Coiflet features /// if(firstOF == false && volumeF == false && textureF == false) { // } else { if(finalWavs.size() != 4*wLevels || finalSegs.size() != 4*wLevels) { MITK_ERROR << "Size of list of segmentations or of the wavelet-files are not as expected. Aborting."; return; } for(int i = wLevels; i > 0; i--) { for(int j = 0; j < 4; j++) { int index = (i-1) * 4 + j; std::string waveletType = "Lowpass"; if(j == 3) waveletType = "H"; else if(j == 2) waveletType = "V"; else if(j == 1) waveletType = "D"; MITK_WARN << "Calculating Coiflet features (Level " << i << "/" << waveletType << ")."; MITK_WARN << "Calulating features for Coiflet " << i << "/" <setup(); std::vector wavs = wavelets->createDaubechiesWavelets(image, wLevels,useStationary); std::vector finalSegs; //Contains the fitted and shrinked segs std::vector finalWavs; //Contains the wavelets in the same order as the finalSegs if(wavs.size() == 0) { MITK_ERROR << "Wavelet calculation failed. Aborting."; return; } else { for(int i = wLevels; i > 0; i--) { mitk::DataNode::Pointer tmpWavNode = mitk::DataNode::New(); mitk::Image::Pointer empty = mitk::Image::New(); empty->Initialize(image); tmpWavNode->SetData(empty); tmpWavNode->SetVisibility(false); tmpWavNode->SetName("Daubechies Level " + std::to_string(wLevels - i + 1 )); for(int j = 0; j < 4; j++) { int index = (i-1) * 4 + j; //Let's fit the segmentation to the wavelet image mitk::Image::Pointer finalSeg = mitk::SegmentationTools::makeRoiFitToImage(wavs[index],segmentation->Clone()); finalSegs.push_back(finalSeg); finalWavs.push_back(wavs[index]); std::string waveletType = "Lowpass"; if(j == 3) waveletType = "H"; else if(j == 2) waveletType = "V"; else if(j == 1) waveletType = "D"; mitk::DataNode::Pointer tmpImageNode = mitk::DataNode::New(); tmpImageNode->SetData(wavs[index]); tmpImageNode->SetName("Image " + waveletType); tmpImageNode->SetVisibility(false); mitk::DataNode::Pointer tmpSegNode = mitk::DataNode::New(); tmpSegNode->SetData(finalSeg); //tmpSegNode->SetData(segs[index]); tmpSegNode->SetName("Segmentation " + waveletType); tmpSegNode->SetVisibility(false); } } } /// /// Calculate the Daubechies features /// if(firstOF == false && volumeF == false && textureF == false) { // } else { if(finalWavs.size() != 4*wLevels || finalSegs.size() != 4*wLevels) { MITK_ERROR << "Size of list of segmentations or of the wavelet-files are not as expected. Aborting."; return; } for(int i = wLevels; i > 0; i--) { for(int j = 0; j < 4; j++) { int index = (i-1) * 4 + j; std::string waveletType = "Lowpass"; if(j == 3) waveletType = "H"; else if(j == 2) waveletType = "V"; else if(j == 1) waveletType = "D"; MITK_WARN << "Calculating Daubechies features (Level " << i << "/" << waveletType << ")."; MITK_WARN << "Calulating features for Daubechies " << i<< "/" < parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.size()==0 || parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << "===" << parser.getTitle() << "===" << endl; std::cout << parser.versionText() << endl; std::cout << "MiniApp Description: \nPerforms a radiomics pipeline on the given input image and mask" << endl; std::cout << "\n\nParameters:" << endl; std::cout << parser.helpText(); return EXIT_SUCCESS; } std::string inputPath = us::any_cast(parsedArgs["input"]); std::string maskPath = us::any_cast(parsedArgs["mask"]); std::string outputPath = us::any_cast(parsedArgs["output"]); bool useNorm = false; bool useGeom = false; bool useRes = false; float resX = 0.25; float resY = 0.25; float resZ = 3.00; bool useDaub = false; bool useCoif = false; int wLevels = 2; bool useStationary = false; bool firstOF = true; bool volumeF = true; bool textureF= true; bool calc2D = false; std::string coocString = "1;2;3"; std::string textureBins= "64;128;256"; //Parse the arguments if (parsedArgs.count("norm") || parsedArgs.count("n")) { useNorm = us::any_cast(parsedArgs["norm"]); } if (parsedArgs.count("adjustgeometry") || parsedArgs.count("g")) { useGeom = us::any_cast(parsedArgs["adjustgeometry"]); } if (parsedArgs.count("resample") || parsedArgs.count("r")) { useRes = us::any_cast(parsedArgs["resample"]); } if (parsedArgs.count("resX") || parsedArgs.count("x")) { resX = us::any_cast(parsedArgs["resX"]); } if (parsedArgs.count("resY") || parsedArgs.count("y")) { resY = us::any_cast(parsedArgs["resY"]); } if (parsedArgs.count("resZ") || parsedArgs.count("z")) { resZ = us::any_cast(parsedArgs["resZ"]); } if (parsedArgs.count("daubechies") || parsedArgs.count("d")) { useDaub = us::any_cast(parsedArgs["daubechies"]); } if (parsedArgs.count("coiflet") || parsedArgs.count("c")) { useCoif = us::any_cast(parsedArgs["coiflet"]); } if (parsedArgs.count("waveletlevel") || parsedArgs.count("w")) { wLevels = us::any_cast(parsedArgs["waveletlevel"]); } if (parsedArgs.count("stationary") || parsedArgs.count("sw")) { useStationary = us::any_cast(parsedArgs["stationary"]); } if (parsedArgs.count("firstorder") || parsedArgs.count("f")) { firstOF = us::any_cast(parsedArgs["firstorder"]); } if (parsedArgs.count("volumetric") || parsedArgs.count("v")) { volumeF = us::any_cast(parsedArgs["volumetric"]); } if (parsedArgs.count("texture") || parsedArgs.count("t")) { textureF = us::any_cast(parsedArgs["texture"]); } if (parsedArgs.count("calculate2D") || parsedArgs.count("xy")) { calc2D = us::any_cast(parsedArgs["calculate2D"]); } if (parsedArgs.count("coocsteps") || parsedArgs.count("s")) { coocString = us::any_cast(parsedArgs["coocsteps"]); } if (parsedArgs.count("texturebins") || parsedArgs.count("b")) { textureBins = us::any_cast(parsedArgs["texturebins"]); } radiomicsPipeline(inputPath, maskPath, outputPath, useNorm, useGeom, useRes, resX, resY, resZ , useDaub, useCoif, wLevels, useStationary, firstOF, volumeF, textureF, calc2D, coocString, textureBins); return EXIT_SUCCESS; }