diff --git a/Modules/Segmentation/Algorithms/itkAdaptiveThresholdIterator.txx b/Modules/Segmentation/Algorithms/itkAdaptiveThresholdIterator.txx index 7513df6272..cf9855e799 100644 --- a/Modules/Segmentation/Algorithms/itkAdaptiveThresholdIterator.txx +++ b/Modules/Segmentation/Algorithms/itkAdaptiveThresholdIterator.txx @@ -1,422 +1,422 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _itkAdaptiveThresholdIterator_txx #define _itkAdaptiveThresholdIterator_txx #include "itkAdaptiveThresholdIterator.h" #include "mitkProgressBar.h" #include <cmath> namespace itk { template <class TImage, class TFunction> AdaptiveThresholdIterator<TImage, TFunction>::AdaptiveThresholdIterator(ImageType *imagePtr, FunctionType *fnPtr, IndexType startIndex) : m_FineDetectionMode(false), m_DetectionStop(false) { this->m_OutputImage = imagePtr; m_Function = fnPtr; m_StartIndices.push_back(startIndex); // Set up the temporary image this->InitializeIterator(); } template <class TImage, class TFunction> AdaptiveThresholdIterator<TImage, TFunction>::AdaptiveThresholdIterator(ImageType *imagePtr, FunctionType *fnPtr, std::vector<IndexType> &startIndex) : m_FineDetectionMode(false), m_DetectionStop(false) { this->m_OutputImage = imagePtr; m_Function = fnPtr; unsigned int i; for (i = 0; i < startIndex.size(); i++) { m_StartIndices.push_back(startIndex[i]); } // Set up the temporary image this->InitializeIterator(); } template <class TImage, class TFunction> AdaptiveThresholdIterator<TImage, TFunction>::AdaptiveThresholdIterator(ImageType *imagePtr, FunctionType *fnPtr) : m_FineDetectionMode(false), m_DetectionStop(false) { this->m_OutputImage = imagePtr; // here we store the image, we have to wite the result to m_Function = fnPtr; // Set up the temporary image this->InitializeIterator(); } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::InitializeIterator() { // Get the origin and spacing from the image in simple arrays m_ImageOrigin = this->m_OutputImage->GetOrigin(); m_ImageSpacing = this->m_OutputImage->GetSpacing(); m_ImageRegion = this->m_OutputImage->GetBufferedRegion(); this->InitRegionGrowingState(); m_VoxelCounter = 0; m_LastVoxelNumber = 0; m_CurrentLeakageRatio = 0; m_DetectedLeakagePoint = 0; } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::SetExpansionDirection(bool upwards) { m_UpwardsExpansion = upwards; } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::IncrementRegionGrowingState() { // make the progressbar go one step further if (!m_FineDetectionMode) mitk::ProgressBar::GetInstance()->Progress(); // updating the thresholds if (m_UpwardsExpansion) { this->ExpandThresholdUpwards(); } else { this->ExpandThresholdDownwards(); } // leakage-detection int criticalValue = 2000; // calculate a bit more "intelligent" if (!m_FineDetectionMode) { int diff = m_VoxelCounter - m_LastVoxelNumber; if (diff > m_CurrentLeakageRatio) { m_CurrentLeakageRatio = diff; m_DetectedLeakagePoint = m_RegionGrowingState; } m_LastVoxelNumber = m_VoxelCounter; m_VoxelCounter = 0; } else // fine leakage detection { // counting voxels over interations; if above a critical value (to be extended) then set this to leakage int diff = m_VoxelCounter - m_LastVoxelNumber; // std::cout<<"diff is: "<<diff<<"\n"; if (diff <= criticalValue && (!m_DetectionStop)) { // m_CurrentLeakageRatio = diff; m_DetectedLeakagePoint = m_RegionGrowingState + 1; // TODO check why this is needed // std::cout<<"setting CurrentLeakageRatio to: "<<diff<<" and leakagePoint to: "<<m_DetectedLeakagePoint<<"\n"; } else { m_DetectionStop = true; // std::cout<<"\n\n[ITERATOR] detection stop!!!\n"; } m_LastVoxelNumber = m_VoxelCounter; m_VoxelCounter = 0; } // increment the counter m_RegionGrowingState++; } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::ExpandThresholdUpwards() { int upper = (int)m_Function->GetUpper(); upper++; m_Function->ThresholdBetween(m_MinTH, upper); } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::ExpandThresholdDownwards() { int lower = (int)m_Function->GetLower(); lower--; m_Function->ThresholdBetween(lower, m_MaxTH); } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::InitRegionGrowingState() { this->m_RegionGrowingState = 1; } template <class TImage, class TFunction> int AdaptiveThresholdIterator<TImage, TFunction>::EstimateDistance(IndexType tempIndex) { PixelType value = this->m_Function->GetInputImage()->GetPixel(tempIndex); PixelType minPixel = PixelType(m_MinTH); PixelType maxPixel = PixelType(m_MaxTH); if (value > maxPixel || value < minPixel) { return 0; } if (m_UpwardsExpansion) { return (int)(value - m_SeedPointValue); } else { return (int)(m_SeedPointValue - value); } } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::SetMinTH(int min) { m_MinTH = min; } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::SetMaxTH(int max) { m_MaxTH = max; } template <class TImage, class TFunction> int AdaptiveThresholdIterator<TImage, TFunction>::GetSeedPointValue() { return this->m_SeedPointValue; } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::GoToBegin() { m_QueueMap.clear(); m_SeedPointValue = 0; IndexType seedIndex = m_StartIndices[0]; bool doAverage = false; // enable or disable manually! if (doAverage) { // loops for creating the sum of the N27-neighborhood around the seedpoint for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { for (int k = -1; k <= 1; k++) { seedIndex[0] = seedIndex[0] + i; seedIndex[1] = seedIndex[1] + j; seedIndex[2] = seedIndex[2] + k; m_SeedPointValue += (int)m_Function->GetInputImage()->GetPixel(seedIndex); } } } // value of seedpoint computed from mean of N27-neighborhood m_SeedPointValue = m_SeedPointValue / 27; } else { m_SeedPointValue = (int)m_Function->GetInputImage()->GetPixel(seedIndex); } this->CheckSeedPointValue(); m_InitializeValue = (this->CalculateMaxRGS() + 1); if (!m_FineDetectionMode) mitk::ProgressBar::GetInstance()->AddStepsToDo(m_InitializeValue - 1); // only initialize with zeros for the first segmention (raw segmentation mode) if (!m_FineDetectionMode) { this->m_OutputImage->FillBuffer((PixelType)0); } if (m_UpwardsExpansion) { m_Function->ThresholdBetween(m_MinTH, m_SeedPointValue); } else { m_Function->ThresholdBetween(m_SeedPointValue, m_MaxTH); } this->m_IsAtEnd = true; seedIndex = m_StartIndices[0]; // warum noch mal? Steht doch schon in Zeile 224 - if (this->m_OutputImage->GetBufferedRegion().IsInside(seedIndex) && this->m_SeedPointValue > this->m_MinTH && - this->m_SeedPointValue < this->m_MaxTH) + if (this->m_OutputImage->GetBufferedRegion().IsInside(seedIndex) && this->m_SeedPointValue >= this->m_MinTH && + this->m_SeedPointValue <= this->m_MaxTH) { // Push the seed onto the queue this->InsertIndexTypeIntoQueueMap(m_RegionGrowingState, seedIndex); // Obviously, we're at the beginning this->m_IsAtEnd = false; this->m_OutputImage->SetPixel(seedIndex, (m_InitializeValue - m_RegionGrowingState)); } } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::CheckSeedPointValue() { // checks, if the value, that has been averaged over the N-27 neighborhood aorund the seedpoint is still inside // the thresholds-ranges. if not, the actual value of the seedpoint (not averaged) is used if (m_SeedPointValue < m_MinTH || m_SeedPointValue > m_MaxTH) { m_SeedPointValue = (int)m_Function->GetInputImage()->GetPixel(m_StartIndices[0]); } } template <class TImage, class TFunction> unsigned int AdaptiveThresholdIterator<TImage, TFunction>::CalculateMaxRGS() { if (m_UpwardsExpansion) { return (m_MaxTH - m_SeedPointValue); } else { return (m_SeedPointValue - m_MinTH); } } template <class TImage, class TFunction> bool AdaptiveThresholdIterator<TImage, TFunction>::IsPixelIncluded(const IndexType &index) const { // checks, if grayvalue of current voxel is inside the currently used thresholds return this->m_Function->EvaluateAtIndex(index); } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::InsertIndexTypeIntoQueueMap(unsigned int key, IndexType index) { // first check if the key-specific queue already exists if (m_QueueMap.count(key) == 0) { // if queue doesn´t exist, create it, push the IndexType onto it // and insert it into the map IndexQueueType newQueue; newQueue.push(index); typedef typename QueueMapType::value_type KeyIndexQueue; m_QueueMap.insert(KeyIndexQueue(key, newQueue)); } else { // if queue already exists in the map, push IndexType onto its specific queue (*(m_QueueMap.find(key))).second.push(index); } } template <class TImage, class TFunction> void AdaptiveThresholdIterator<TImage, TFunction>::DoExtendedFloodStep() { // The index in the front of the queue should always be // valid and be inside since this is what the iterator // uses in the Set/Get methods. This is ensured by the // GoToBegin() method. typename QueueMapType::iterator currentIt = m_QueueMap.find(m_RegionGrowingState); if (currentIt == m_QueueMap.end()) { this->IncrementRegionGrowingState(); } else { IndexQueueType *currentQueue = &(*currentIt).second; // Take the index in the front of the queue const IndexType &topIndex = currentQueue->front(); // Iterate through all possible dimensions // NOTE: Replace this with a ShapeNeighborhoodIterator for (unsigned int i = 0; i < NDimensions; i++) { // The j loop establishes either left or right neighbor (+-1) for (int j = -1; j <= 1; j += 2) { IndexType tempIndex; // build the index of a neighbor for (unsigned int k = 0; k < NDimensions; k++) { if (i != k) { tempIndex.m_Index[k] = topIndex[k]; } else { tempIndex.m_Index[k] = topIndex[k] + j; } } // end build the index of a neighbor // If this is a valid index and have not been tested, // then test it. if (m_ImageRegion.IsInside(tempIndex)) { // check if voxel hasn´t already been processed if (this->m_OutputImage->GetPixel(tempIndex) == 0) { // if it is inside, push it into the queue if (this->IsPixelIncluded(tempIndex)) { // hier wird Voxel in momentan aktiven Stack und ins OutputImage geschrieben this->InsertIndexTypeIntoQueueMap((m_RegionGrowingState), tempIndex); this->m_OutputImage->SetPixel(tempIndex, (m_InitializeValue - m_RegionGrowingState)); } else // If the pixel is not inside the current threshold { int distance = this->EstimateDistance( tempIndex); // [!] sollte nicht estimateDistance sondern calculateDistance() heißen! if (distance != 0) { // hier wird Voxel in entsprechenden Stack und ins OutputImage geschrieben this->InsertIndexTypeIntoQueueMap((distance), tempIndex); this->m_OutputImage->SetPixel(tempIndex, (m_InitializeValue - distance)); } } } } } // end left/right neighbor loop } // end check all neighbors // Now that all the potential neighbors have been // inserted we can get rid of the pixel in the front currentQueue->pop(); m_VoxelCounter++; if (currentQueue->empty()) { // if currently used queue is empty this->IncrementRegionGrowingState(); } } if (m_RegionGrowingState >= (m_InitializeValue) || m_DetectionStop) { this->m_IsAtEnd = true; // std::cout << "RegionGrowing finished !" << std::endl; // std::cout << "Detected point of leakage: " << m_DetectedLeakagePoint << std::endl; } } } // end namespace itk #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp index a20b0fcc50..b7afba8afc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp @@ -1,995 +1,1014 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkAdaptiveRegionGrowingToolGUI.h" #include <qmessagebox.h> #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateDataType.h" #include "mitkProperties.h" #include "mitkTransferFunctionProperty.h" #include "mitkImageStatisticsHolder.h" #include "itkMaskImageFilter.h" #include "itkNumericTraits.h" #include <itkBinaryThresholdImageFilter.h> #include <itkConnectedAdaptiveThresholdImageFilter.h> #include <itkImageIterator.h> #include <itkMinimumMaximumImageCalculator.h> #include "QmitkConfirmSegmentationDialog.h" #include "itkOrImageFilter.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkImageCast.h" MITK_TOOL_GUI_MACRO(, QmitkAdaptiveRegionGrowingToolGUI, "") QmitkAdaptiveRegionGrowingToolGUI::QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent) : QmitkToolGUI(), m_DataStorage(nullptr), m_UseVolumeRendering(false), m_UpdateSuggestedThreshold(true), m_SuggestedThValue(0.0) { this->setParent(parent); m_Controls.setupUi(this); m_Controls.m_ThresholdSlider->setDecimals(1); m_Controls.m_ThresholdSlider->setSpinBoxAlignment(Qt::AlignVCenter); m_Controls.m_PreviewSlider->setEnabled(false); m_Controls.m_PreviewSlider->setSingleStep(0.5); // Not yet available // m_Controls.m_PreviewSlider->InvertedAppearance(true); //3D preview doesn't work: T24430. Postponed until reimplementation of segmentation m_Controls.m_cbVolumeRendering->setVisible(false); this->CreateConnections(); this->SetDataNodeNames("labeledRGSegmentation", "RGResult", "RGFeedbackSurface", "maskedSegmentation"); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAdaptiveRegionGrowingToolGUI::~QmitkAdaptiveRegionGrowingToolGUI() { // Removing the observer of the PointSet node if (m_RegionGrow3DTool->GetPointSetNode().IsNotNull()) { m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetAddObserverTag); m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetMoveObserverTag); } this->RemoveHelperNodes(); } void QmitkAdaptiveRegionGrowingToolGUI::OnNewToolAssociated(mitk::Tool *tool) { m_RegionGrow3DTool = dynamic_cast<mitk::AdaptiveRegionGrowingTool *>(tool); if (m_RegionGrow3DTool.IsNotNull()) { SetInputImageNode(this->m_RegionGrow3DTool->GetReferenceData()); this->m_DataStorage = this->m_RegionGrow3DTool->GetDataStorage(); this->EnableControls(true); // Watch for point added or modified itk::SimpleMemberCommand<QmitkAdaptiveRegionGrowingToolGUI>::Pointer pointAddedCommand = itk::SimpleMemberCommand<QmitkAdaptiveRegionGrowingToolGUI>::New(); pointAddedCommand->SetCallbackFunction(this, &QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded); m_PointSetAddObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); m_PointSetMoveObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetMoveEvent(), pointAddedCommand); } else { this->EnableControls(false); } } void QmitkAdaptiveRegionGrowingToolGUI::RemoveHelperNodes() { mitk::DataNode::Pointer imageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (imageNode.IsNotNull()) { m_DataStorage->Remove(imageNode); } mitk::DataNode::Pointer maskedSegmentationNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (maskedSegmentationNode.IsNotNull()) { m_DataStorage->Remove(maskedSegmentationNode); } } void QmitkAdaptiveRegionGrowingToolGUI::CreateConnections() { // Connecting GUI components connect((QObject *)(m_Controls.m_pbRunSegmentation), SIGNAL(clicked()), this, SLOT(RunSegmentation())); connect(m_Controls.m_PreviewSlider, SIGNAL(valueChanged(double)), this, SLOT(ChangeLevelWindow(double))); connect((QObject *)(m_Controls.m_pbConfirmSegementation), SIGNAL(clicked()), this, SLOT(ConfirmSegmentation())); connect( m_Controls.m_ThresholdSlider, SIGNAL(maximumValueChanged(double)), this, SLOT(SetUpperThresholdValue(double))); connect( m_Controls.m_ThresholdSlider, SIGNAL(minimumValueChanged(double)), this, SLOT(SetLowerThresholdValue(double))); } void QmitkAdaptiveRegionGrowingToolGUI::SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, std::string surface, std::string maskedSegmentation) { m_NAMEFORLABLEDSEGMENTATIONIMAGE = labledSegmentation; m_NAMEFORBINARYIMAGE = binaryImage; m_NAMEFORSURFACE = surface; m_NAMEFORMASKEDSEGMENTATION = maskedSegmentation; } void QmitkAdaptiveRegionGrowingToolGUI::SetDataStorage(mitk::DataStorage *dataStorage) { m_DataStorage = dataStorage; } void QmitkAdaptiveRegionGrowingToolGUI::SetInputImageNode(mitk::DataNode *node) { m_InputImageNode = node; mitk::Image *inputImage = dynamic_cast<mitk::Image *>(m_InputImageNode->GetData()); if (inputImage) { mitk::ScalarType max = inputImage->GetStatistics()->GetScalarValueMax(); mitk::ScalarType min = inputImage->GetStatistics()->GetScalarValueMin(); m_Controls.m_ThresholdSlider->setMaximum(max); m_Controls.m_ThresholdSlider->setMinimum(min); // Just for initialization m_Controls.m_ThresholdSlider->setMaximumValue(max); m_Controls.m_ThresholdSlider->setMinimumValue(min); } } template <typename TPixel> static void AccessPixel(mitk::PixelType /*ptype*/, mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor<TPixel, 3> access(im); val = access.GetPixelByWorldCoordinates(p); } /**Overloaded const verison*/ template <typename TPixel> static void AccessPixel(mitk::PixelType /*ptype*/, const mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor<TPixel, 3> access(im); val = access.GetPixelByWorldCoordinates(p); } void QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded() { if (m_RegionGrow3DTool.IsNull()) return; mitk::DataNode *node = m_RegionGrow3DTool->GetPointSetNode(); if (node != nullptr) { mitk::PointSet::Pointer pointSet = dynamic_cast<mitk::PointSet *>(node->GetData()); if (pointSet.IsNull()) { QMessageBox::critical(nullptr, "QmitkAdaptiveRegionGrowingToolGUI", "PointSetNode does not contain a pointset"); return; } m_Controls.m_lblSetSeedpoint->setText(""); const mitk::Image *image = dynamic_cast<mitk::Image *>(m_InputImageNode->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto image3D = GetImageByTimePoint(image, timePoint); if (nullptr == image3D) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected " "reference image. Time point: " << timePoint; return; } if (!pointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; mitk::Point3D seedPoint = pointSet ->GetPointSet(static_cast<int>(pointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint))) ->GetPoints() ->ElementAt(0); if (image3D->GetGeometry()->IsInside(seedPoint)) mitkPixelTypeMultiplex3( AccessPixel, image3D->GetChannelDescriptor().GetPixelType(), image3D, seedPoint, m_SeedpointValue) else return; /* In this case the seedpoint is placed e.g. in the lung or bronchialtree * The lowerFactor sets the windowsize depending on the regiongrowing direction */ m_CurrentRGDirectionIsUpwards = true; if (m_SeedpointValue < -500) { m_CurrentRGDirectionIsUpwards = false; } // Initializing the region by the area around the seedpoint m_SeedPointValueMean = 0; itk::Index<3> currentIndex, runningIndex; mitk::ScalarType pixelValues[125]; unsigned int pos(0); image3D->GetGeometry(0)->WorldToIndex(seedPoint, currentIndex); runningIndex = currentIndex; for (int i = runningIndex[0] - 2; i <= runningIndex[0] + 2; i++) { for (int j = runningIndex[1] - 2; j <= runningIndex[1] + 2; j++) { for (int k = runningIndex[2] - 2; k <= runningIndex[2] + 2; k++) { currentIndex[0] = i; currentIndex[1] = j; currentIndex[2] = k; if (image3D->GetGeometry()->IsIndexInside(currentIndex)) { int component = 0; m_InputImageNode->GetIntProperty("Image.Displayed Component", component); mitkPixelTypeMultiplex4(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, nullptr, currentIndex, pixelValues[pos]); pos++; } else { pixelValues[pos] = std::numeric_limits<long>::min(); pos++; } } } } // Now calculation mean of the pixelValues // Now calculation mean of the pixelValues unsigned int numberOfValues(0); for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits<long>::min()) { m_SeedPointValueMean += pixelValue; numberOfValues++; } } m_SeedPointValueMean = m_SeedPointValueMean / numberOfValues; mitk::ScalarType var = 0; if (numberOfValues > 1) { for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits<mitk::ScalarType>::min()) { var += (pixelValue - m_SeedPointValueMean) * (pixelValue - m_SeedPointValueMean); } } var /= numberOfValues - 1; } mitk::ScalarType stdDev = sqrt(var); /* * Here the upper- and lower threshold is calculated: * The windowSize is 20% of the maximum range of the intensity values existing in the current image * If the RG direction is upwards the lower TH is meanSeedValue-0.15*windowSize and upper TH is * meanSeedValue+0.85*windowsSize * if the RG direction is downwards the lower TH is meanSeedValue-0.85*windowSize and upper TH is * meanSeedValue+0.15*windowsSize */ const auto timeStepOfImage = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ScalarType min = image->GetStatistics()->GetScalarValueMin(timeStepOfImage); mitk::ScalarType max = image->GetStatistics()->GetScalarValueMax(timeStepOfImage); mitk::ScalarType windowSize = max - min; windowSize = 0.15 * windowSize; if (m_CurrentRGDirectionIsUpwards) { m_LOWERTHRESHOLD = m_SeedPointValueMean - stdDev; m_UPPERTHRESHOLD = m_SeedpointValue + windowSize; if (m_UPPERTHRESHOLD > max) m_UPPERTHRESHOLD = max; m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); } else { m_UPPERTHRESHOLD = m_SeedPointValueMean; if (m_SeedpointValue > m_SeedPointValueMean) m_UPPERTHRESHOLD = m_SeedpointValue; m_LOWERTHRESHOLD = m_SeedpointValue - windowSize; if (m_LOWERTHRESHOLD < min) m_LOWERTHRESHOLD = min; m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); } } } mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; if (image->GetDimension() != 4) return image; auto imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast<int>(image->GetTimeGeometry()->TimePointToTimeStep(timePoint))); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } void QmitkAdaptiveRegionGrowingToolGUI::RunSegmentation() { if (m_InputImageNode.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please specify the image in Datamanager!"); return; } mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please insert a seed point inside the " "image.\n\nFirst press the \"Define Seed " "Point\" button,\nthen click left mouse " "button inside the image."); return; } // safety if no pointSet or pointSet empty mitk::PointSet::Pointer seedPointSet = dynamic_cast<mitk::PointSet *>(node->GetData()); if (seedPointSet.IsNull()) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } mitk::Image::Pointer orgImage = dynamic_cast<mitk::Image *>(m_InputImageNode->GetData()); if (orgImage.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!seedPointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Point set is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast<int>(seedPointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (!(seedPointSet->GetSize(timeStep))) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); mitk::PointSet::PointType seedPoint = seedPointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); auto image3D = GetImageByTimePoint(orgImage, timePoint); if (image3D.IsNotNull()) { // QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //set the cursor to waiting AccessByItk_2(image3D, StartRegionGrowing, image3D->GetGeometry(), seedPoint); // QApplication::restoreOverrideCursor();//reset cursor } else { QApplication::restoreOverrideCursor(); // reset cursor QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "Only images of dimension 3 or 4 can be processed!"); return; } } EnableControls(true); // Segmentation ran successfully, so enable all controls. node->SetVisibility(true); QApplication::restoreOverrideCursor(); // reset cursor } template <typename TPixel, unsigned int VImageDimension> void QmitkAdaptiveRegionGrowingToolGUI::StartRegionGrowing(const itk::Image<TPixel, VImageDimension> *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint) { typedef itk::Image<TPixel, VImageDimension> InputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedAdaptiveThresholdImageFilter<InputImageType, InputImageType> RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); typedef itk::BinaryThresholdImageFilter<InputImageType, InputImageType> ThresholdFilterType; typedef itk::MaskImageFilter<InputImageType, InputImageType, InputImageType> MaskImageFilterType; if (!imageGeometry->IsInside(seedPoint)) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information(nullptr, "Segmentation functionality", "The seed point is outside of the image! Please choose a position inside the image!"); return; } IndexType seedIndex; imageGeometry->WorldToIndex(seedPoint, seedIndex); // convert world coordinates to image indices if (m_SeedpointValue > m_UPPERTHRESHOLD || m_SeedpointValue < m_LOWERTHRESHOLD) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information( nullptr, "Segmentation functionality", "The seed point is outside the defined thresholds! Please set a new seed point or adjust the thresholds."); MITK_INFO << "Mean: " << m_SeedPointValueMean; return; } // Setting the direction of the regiongrowing. For dark structures e.g. the lung the regiongrowing // is performed starting at the upper value going to the lower one regionGrower->SetGrowingDirectionIsUpwards(m_CurrentRGDirectionIsUpwards); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); // In some cases we have to subtract 1 for the lower threshold and add 1 to the upper. // Otherwise no region growing is done. Maybe a bug in the ConnectiveAdaptiveThresholdFilter - regionGrower->SetLower(m_LOWERTHRESHOLD - 1); - regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); + mitk::ScalarType maxPixelValue = m_Controls.m_ThresholdSlider->maximum(); + mitk::ScalarType minPixelValue = m_Controls.m_ThresholdSlider->minimum(); + + if ((m_LOWERTHRESHOLD - minPixelValue) >= 1) + { + regionGrower->SetLower(m_LOWERTHRESHOLD - 1); + } + else + { + regionGrower->SetLower(m_LOWERTHRESHOLD); + } + + if ((maxPixelValue - m_UPPERTHRESHOLD) >= 1) + { + regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); + } + else + { + regionGrower->SetUpper(m_UPPERTHRESHOLD); + } + try { regionGrower->Update(); } catch (itk::ExceptionObject &exc) { QMessageBox errorInfo; errorInfo.setWindowTitle("Adaptive RG Segmentation Functionality"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during region growing!"); errorInfo.setDetailedText(exc.what()); errorInfo.exec(); return; // can't work } catch (...) { QMessageBox::critical(nullptr, "Adaptive RG Segmentation Functionality", "An error occurred during region growing!"); return; } mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput())->Clone(); // initialize slider m_Controls.m_PreviewSlider->setMinimum(m_LOWERTHRESHOLD); mitk::ScalarType max = m_SeedpointValue + resultImage->GetStatistics()->GetScalarValueMax(); if (max < m_UPPERTHRESHOLD) m_Controls.m_PreviewSlider->setMaximum(max); else m_Controls.m_PreviewSlider->setMaximum(m_UPPERTHRESHOLD); this->m_DetectedLeakagePoint = regionGrower->GetLeakagePoint(); if (m_CurrentRGDirectionIsUpwards) { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean - 1); } else { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean + 1); } this->m_SliderInitialized = true; // create new node and then delete the old one if there is one mitk::DataNode::Pointer newNode = mitk::DataNode::New(); newNode->SetData(resultImage); // set some properties newNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORLABLEDSEGMENTATIONIMAGE)); newNode->SetProperty("helper object", mitk::BoolProperty::New(true)); newNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); newNode->SetProperty("layer", mitk::IntProperty::New(1)); newNode->SetProperty("opacity", mitk::FloatProperty::New(0.7)); // delete the old image, if there was one: mitk::DataNode::Pointer binaryNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); m_DataStorage->Remove(binaryNode); // now add result to data tree m_DataStorage->Add(newNode, m_InputImageNode); typename InputImageType::Pointer inputImageItk; mitk::CastToItkImage<InputImageType>(resultImage, inputImageItk); // volume rendering preview masking typename ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(inputImageItk); thresholdFilter->SetInsideValue(1); thresholdFilter->SetOutsideValue(0); double sliderVal = this->m_Controls.m_PreviewSlider->value(); if (m_CurrentRGDirectionIsUpwards) { thresholdFilter->SetLowerThreshold(sliderVal); thresholdFilter->SetUpperThreshold(itk::NumericTraits<TPixel>::max()); } else { thresholdFilter->SetLowerThreshold(itk::NumericTraits<TPixel>::min()); thresholdFilter->SetUpperThreshold(sliderVal); } thresholdFilter->SetInPlace(false); typename MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(inputImageItk); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMask; mitk::CastToMitkImage<InputImageType>(maskFilter->GetOutput(), mitkMask); mitk::DataNode::Pointer maskedNode = mitk::DataNode::New(); maskedNode->SetData(mitkMask); // set some properties maskedNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORMASKEDSEGMENTATION)); maskedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); maskedNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); maskedNode->SetProperty("layer", mitk::IntProperty::New(1)); maskedNode->SetProperty("opacity", mitk::FloatProperty::New(0.0)); // delete the old image, if there was one: mitk::DataNode::Pointer deprecatedMask = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); m_DataStorage->Remove(deprecatedMask); // now add result to data tree m_DataStorage->Add(maskedNode, m_InputImageNode); this->InitializeLevelWindow(); if (m_UseVolumeRendering) this->EnableVolumeRendering(true); m_UpdateSuggestedThreshold = true; // reset first stored threshold value // Setting progress to finished mitk::ProgressBar::GetInstance()->Progress(357); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::InitializeLevelWindow() { // get the preview from the datatree mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); mitk::ScalarType *level = new mitk::ScalarType(0.0); mitk::ScalarType *window = new mitk::ScalarType(1.0); int upper; if (m_CurrentRGDirectionIsUpwards) { upper = m_UPPERTHRESHOLD - m_SeedpointValue; } else { upper = m_SeedpointValue - m_LOWERTHRESHOLD; } tempLevelWindow.SetRangeMinMax(mitk::ScalarType(0), mitk::ScalarType(upper)); // get the suggested threshold from the detected leakage-point and adjust the slider if (m_CurrentRGDirectionIsUpwards) { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = m_UPPERTHRESHOLD - (m_SeedpointValue) + 0.5; } else { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = (m_SeedpointValue)-m_LOWERTHRESHOLD + 0.5; } tempLevelWindow.SetLevelWindow(*level, *window); newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // update the widgets mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_SliderInitialized = true; // inquiry need to fix bug#1828 static int lastSliderPosition = 0; if ((this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1) == lastSliderPosition) { this->ChangeLevelWindow(lastSliderPosition); } lastSliderPosition = this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1; if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(*level + 0.5)); // lower threshold for labeled image } void QmitkAdaptiveRegionGrowingToolGUI::ChangeLevelWindow(double newValue) { if (m_SliderInitialized) { // do nothing, if no preview exists mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // get the levelWindow associated with the preview mitk::ScalarType level; // = this->m_UPPERTHRESHOLD - newValue + 0.5; mitk::ScalarType *window = new mitk::ScalarType(1); // adjust the levelwindow according to the position of the slider (newvalue) if (m_CurrentRGDirectionIsUpwards) { level = m_UPPERTHRESHOLD - newValue + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } else { level = newValue - m_LOWERTHRESHOLD + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(level - 0.5)); // lower threshold for labeled image newNode->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::DecreaseSlider() { // moves the slider one step to the left, when the "-"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->minimum()) { int newValue = this->m_Controls.m_PreviewSlider->value() - 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::IncreaseSlider() { // moves the slider one step to the right, when the "+"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->maximum()) { int newValue = this->m_Controls.m_PreviewSlider->value() + 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::ConfirmSegmentation() { // get image node if (m_InputImageNode.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "Please specify the image in Datamanager!"); return; } // get image data mitk::Image::Pointer orgImage = dynamic_cast<mitk::Image *>(m_InputImageNode->GetData()); if (orgImage.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Image found!"); return; } // get labeled segmentation mitk::Image::Pointer labeledSeg = (mitk::Image *)m_DataStorage->GetNamedObject<mitk::Image>(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (labeledSeg.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Segmentation Preview found!"); return; } mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_RegionGrow3DTool->GetCurrentSegmentationName()); dialog.SetSegmentationName(segName); int result = dialog.exec(); switch (result) { case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(false); break; case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(true); break; case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: return; } mitk::Image::Pointer img = dynamic_cast<mitk::Image *>(newNode->GetData()); AccessByItk(img, ITKThresholding); // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); newNode->SetVisibility(false); m_Controls.m_cbVolumeRendering->setChecked(false); // TODO disable slider etc... if (m_RegionGrow3DTool.IsNotNull()) { m_RegionGrow3DTool->ConfirmSegmentation(); } } template <typename TPixel, unsigned int VImageDimension> void QmitkAdaptiveRegionGrowingToolGUI::ITKThresholding(itk::Image<TPixel, VImageDimension> *itkImage) { mitk::Image::Pointer originalSegmentation = dynamic_cast<mitk::Image *>(this->m_RegionGrow3DTool->GetTargetSegmentationNode()->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!originalSegmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast<int>(originalSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (originalSegmentation) { typedef itk::Image<TPixel, VImageDimension> InputImageType; typedef itk::Image<mitk::Tool::DefaultSegmentationDataType, VImageDimension> SegmentationType; // select single 3D volume if we have more than one time step typename SegmentationType::Pointer originalSegmentationInITK = SegmentationType::New(); if (originalSegmentation->GetTimeGeometry()->CountTimeSteps() > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(originalSegmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); CastToItkImage(timeSelector->GetOutput(), originalSegmentationInITK); } else // use original { CastToItkImage(originalSegmentation, originalSegmentationInITK); } // Fill current preiview image in segmentation image originalSegmentationInITK->FillBuffer(0); itk::ImageRegionIterator<SegmentationType> itOutput(originalSegmentationInITK, originalSegmentationInITK->GetLargestPossibleRegion()); itk::ImageRegionIterator<InputImageType> itInput(itkImage, itkImage->GetLargestPossibleRegion()); itOutput.GoToBegin(); itInput.GoToBegin(); // calculate threhold from slider value int currentTreshold = 0; if (m_CurrentRGDirectionIsUpwards) { currentTreshold = m_UPPERTHRESHOLD - m_Controls.m_PreviewSlider->value() + 1; } else { currentTreshold = m_Controls.m_PreviewSlider->value() - m_LOWERTHRESHOLD; } // iterate over image and set pixel in segmentation according to thresholded labeled image while (!itOutput.IsAtEnd() && !itInput.IsAtEnd()) { // Use threshold slider to determine if pixel is set to 1 if (itInput.Value() != 0 && itInput.Value() >= static_cast<typename itk::ImageRegionIterator<InputImageType>::PixelType>(currentTreshold)) { itOutput.Set(1); } ++itOutput; ++itInput; } // combine current working segmentation image with our region growing result originalSegmentation->SetVolume((void *)(originalSegmentationInITK->GetPixelContainer()->GetBufferPointer()), timeStep); originalSegmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::EnableControls(bool enable) { if (m_RegionGrow3DTool.IsNull()) return; // Check if seed point is already set, if not leave RunSegmentation disabled // if even m_DataStorage is nullptr leave node nullptr mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { this->m_Controls.m_pbRunSegmentation->setEnabled(false); } else { this->m_Controls.m_pbRunSegmentation->setEnabled(enable); } // Check if a segmentation exists, if not leave segmentation dependent disabled. // if even m_DataStorage is nullptr leave node nullptr node = m_DataStorage ? m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE) : nullptr; if (node.IsNull()) { this->m_Controls.m_PreviewSlider->setEnabled(false); this->m_Controls.m_pbConfirmSegementation->setEnabled(false); } else { this->m_Controls.m_PreviewSlider->setEnabled(enable); this->m_Controls.m_pbConfirmSegementation->setEnabled(enable); } this->m_Controls.m_cbVolumeRendering->setEnabled(enable); } void QmitkAdaptiveRegionGrowingToolGUI::EnableVolumeRendering(bool enable) { mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (node.IsNull()) return; if (enable) { node->SetBoolProperty("volumerendering", enable); node->SetBoolProperty("volumerendering.uselod", true); } else { node->SetBoolProperty("volumerendering", enable); } double val = this->m_Controls.m_PreviewSlider->value(); this->ChangeLevelWindow(val); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::UpdateVolumeRenderingThreshold(int) { typedef short PixelType; typedef itk::Image<PixelType, 3> InputImageType; typedef itk::BinaryThresholdImageFilter<InputImageType, InputImageType> ThresholdFilterType; typedef itk::MaskImageFilter<InputImageType, InputImageType, InputImageType> MaskImageFilterType; mitk::DataNode::Pointer grownImageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::Image::Pointer grownImage = dynamic_cast<mitk::Image *>(grownImageNode->GetData()); if (!grownImage) { MITK_ERROR << "Missing data node for labeled segmentation image."; return; } InputImageType::Pointer itkGrownImage; mitk::CastToItkImage(grownImage, itkGrownImage); ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(itkGrownImage); thresholdFilter->SetInPlace(false); double sliderVal = this->m_Controls.m_PreviewSlider->value(); PixelType threshold = itk::NumericTraits<PixelType>::min(); if (m_CurrentRGDirectionIsUpwards) { threshold = static_cast<PixelType>(m_UPPERTHRESHOLD - sliderVal + 0.5); thresholdFilter->SetLowerThreshold(threshold); thresholdFilter->SetUpperThreshold(itk::NumericTraits<PixelType>::max()); } else { threshold = sliderVal - m_LOWERTHRESHOLD + 0.5; thresholdFilter->SetLowerThreshold(itk::NumericTraits<PixelType>::min()); thresholdFilter->SetUpperThreshold(threshold); } thresholdFilter->UpdateLargestPossibleRegion(); MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(itkGrownImage); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMaskedImage; mitk::CastToMitkImage<InputImageType>(maskFilter->GetOutput(), mitkMaskedImage); mitk::DataNode::Pointer maskNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); maskNode->SetData(mitkMaskedImage); } void QmitkAdaptiveRegionGrowingToolGUI::UseVolumeRendering(bool on) { m_UseVolumeRendering = on; this->EnableVolumeRendering(on); } void QmitkAdaptiveRegionGrowingToolGUI::SetLowerThresholdValue(double lowerThreshold) { m_LOWERTHRESHOLD = lowerThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::SetUpperThresholdValue(double upperThreshold) { m_UPPERTHRESHOLD = upperThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::Deactivated() { // make the segmentation preview node invisible mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (node.IsNotNull()) { node->SetVisibility(false); } // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); m_Controls.m_cbVolumeRendering->setChecked(false); } void QmitkAdaptiveRegionGrowingToolGUI::Activated() { } diff --git a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.cpp b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.cpp index 076eb3d29b..c596f9ead7 100644 --- a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.cpp +++ b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.cpp @@ -1,158 +1,144 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include <QmitkDataNodeColorMapAction.h> // mitk core #include <mitkImage.h> #include <mitkLookupTableProperty.h> #include <mitkLookupTable.h> #include <mitkRenderingManager.h> #include <mitkRenderingModeProperty.h> // mitk gui common plugin #include <mitkDataNodeSelection.h> // qt #include <QMenu> QmitkDataNodeColorMapAction::QmitkDataNodeColorMapAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchpartSite) : QAction(parent) , QmitkAbstractDataNodeAction(workbenchpartSite) { setText(tr("Colormap")); InitializeAction(); } QmitkDataNodeColorMapAction::QmitkDataNodeColorMapAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchpartSite) : QAction(parent) , QmitkAbstractDataNodeAction(berry::IWorkbenchPartSite::Pointer(workbenchpartSite)) { setText(tr("Colormap")); InitializeAction(); } void QmitkDataNodeColorMapAction::InitializeAction() { setCheckable(true); setMenu(new QMenu); connect(menu(), &QMenu::aboutToShow, this, &QmitkDataNodeColorMapAction::OnMenuAboutShow); } void QmitkDataNodeColorMapAction::OnMenuAboutShow() { auto dataNode = GetSelectedNode(); if (dataNode.IsNull()) { return; } mitk::BaseRenderer::Pointer baseRenderer = GetBaseRenderer(); mitk::LookupTableProperty::Pointer lookupTableProperty = dynamic_cast<mitk::LookupTableProperty*>(dataNode->GetProperty("LookupTable", baseRenderer)); if (lookupTableProperty.IsNull()) { mitk::LookupTable::Pointer mitkLut = mitk::LookupTable::New(); lookupTableProperty = mitk::LookupTableProperty::New(); lookupTableProperty->SetLookupTable(mitkLut); dataNode->SetProperty("LookupTable", lookupTableProperty, baseRenderer); } mitk::LookupTable::Pointer lookupTable = lookupTableProperty->GetValue(); if (lookupTable.IsNull()) { return; } menu()->clear(); QAction* lutAction; for (const auto& lutTypeString : lookupTable->typenameList) { lutAction = menu()->addAction(QString::fromStdString(lutTypeString)); lutAction->setCheckable(true); if (lutTypeString == lookupTable->GetActiveTypeAsString()) { lutAction->setChecked(true); } connect(lutAction, &QAction::triggered, this, &QmitkDataNodeColorMapAction::OnActionTriggered); } } void QmitkDataNodeColorMapAction::OnActionTriggered(bool /*checked*/) { mitk::BaseRenderer::Pointer baseRenderer = GetBaseRenderer(); auto selectedNodes = GetSelectedNodes(); for (auto& dataNode : selectedNodes) { if (dataNode.IsNull()) { continue; } mitk::LookupTableProperty::Pointer lookupTableProperty = dynamic_cast<mitk::LookupTableProperty*>(dataNode->GetProperty("LookupTable", baseRenderer)); if (lookupTableProperty.IsNull()) { continue; } mitk::LookupTable::Pointer lookupTable = lookupTableProperty->GetValue(); if (lookupTable.IsNull()) { continue; } mitk::LookupTable::Pointer renderWindowSpecificLuT = lookupTable->Clone(); QAction* senderAction = qobject_cast<QAction*>(QObject::sender()); if (nullptr == senderAction) { continue; } // set lookup table type defined by the action string std::string activatedItem = senderAction->text().toStdString(); renderWindowSpecificLuT->SetType(activatedItem); dataNode->SetProperty("LookupTable", mitk::LookupTableProperty::New(renderWindowSpecificLuT), baseRenderer); - if (mitk::LookupTable::LookupTableType::MULTILABEL == lookupTable->GetActiveType()) - { - // special case: multilabel => set the level window to include the whole pixel range - UseWholePixelRange(dataNode); - } - mitk::RenderingModeProperty::Pointer renderingMode = dynamic_cast<mitk::RenderingModeProperty*>(dataNode->GetProperty("Image Rendering.Mode", baseRenderer)); - renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); + + renderingMode->SetValue(mitk::LookupTable::LookupTableType::MULTILABEL != renderWindowSpecificLuT->GetActiveType() + ? mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR + : mitk::RenderingModeProperty::LOOKUPTABLE_COLOR); if (nullptr == baseRenderer) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } } } - -void QmitkDataNodeColorMapAction::UseWholePixelRange(mitk::DataNode* node) -{ - auto image = dynamic_cast<mitk::Image*>(node->GetData()); - if (nullptr != image) - { - mitk::LevelWindow levelWindow; - levelWindow.SetToImageRange(image); - node->SetLevelWindow(levelWindow); - } -} diff --git a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.h b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.h index 9645994164..486829b68c 100644 --- a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.h +++ b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeColorMapAction.h @@ -1,48 +1,46 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QMITKDATANODECOLORMAPACTION_H #define QMITKDATANODECOLORMAPACTION_H #include <org_mitk_gui_qt_application_Export.h> #include "QmitkAbstractDataNodeAction.h" // mitk core #include <mitkDataNode.h> // qt #include <QAction> class MITK_QT_APP QmitkDataNodeColorMapAction : public QAction, public QmitkAbstractDataNodeAction { Q_OBJECT public: QmitkDataNodeColorMapAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); QmitkDataNodeColorMapAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); private Q_SLOTS: void OnMenuAboutShow(); void OnActionTriggered(bool); protected: void InitializeAction() override; - void UseWholePixelRange(mitk::DataNode* node); - }; #endif // QMITKDATANODECOLORMAPACTION_H diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities.dox index 623bfb4c2b..2801ca67e4 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities.dox @@ -1,80 +1,84 @@ /** \page org_mitk_views_segmentationutilities The Segmentation Utilities View \imageMacro{segmentation_utilities-dox.svg,"Icon of the Segmentation Utilities View",5.00} \imageMacro{QmitkSegmentationUtilities_Overview.png,"The Segmentation Utilities View",16.00} \tableofcontents \section org_mitk_views_segmentationUtilitiesManualOverview Overview The <b>Segmentation Utilities View</b> allows to postprocess existing segmentations. Currently five different operations exist: <ul> <li> \ref org_mitk_views_segmentationUtilitiesBooleanOperations <li> \ref org_mitk_views_segmentationUtilitiesContourToImage <li> \ref org_mitk_views_segmentationUtilitiesImageMasking <li> \ref org_mitk_views_segmentationUtilitiesMorphologicalOperations <li> \ref org_mitk_views_segmentationUtilitiesSurfaceToImage </ul> \section org_mitk_views_segmentationUtilitiesDataSelection Data Selection All postprocessing operations provide one or more selection widgets, which allow to select the data for the operation. \section org_mitk_views_segmentationUtilitiesBooleanOperations Boolean operations Boolean operations allows to perform the following fundamental operations on two segmentations: <ul> <li> <b>Difference:</b> Subtracts the second segmentation from the first segmentation. <li> <b>Intersection:</b> Extracts the overlapping areas of the two selected segmentations. <li> <b>Union:</b> Combines the two existing segmentations. </ul> The selected segmentations must have the same geometry (size, spacing, ...) in order for the operations to work correctly. The result will be stored in a new data node as a child node of the first selected segmentation. \imageMacro{QmitkSegmentationUtilities_BooleanOperations.png,"Boolean operations",6.00} \section org_mitk_views_segmentationUtilitiesContourToImage Contour to image Contour to image allows to create a segmentation out of a given contour-model. The operation requires a contour model set and a reference image. The created segmentation image will have the same geometrical properties like the reference image (dimension, size and Geometry3D). \imageMacro{QmitkSegmentationUtilities_ContourToImage.png,"Contour to image",6.00} \section org_mitk_views_segmentationUtilitiesImageMasking Image masking Image masking allows to mask an image with either an existing segmentation or a surface. The operation requires an image and a segmentation or a surface. The result will be an image containing only the pixels that are covered by the respective mask. +The default background pixel value is zero. +It can be changed to the minimum existing pixel value of the image or to a custom pixel value. +If the custom pixel value is out of the valid bounds of the pixel type, it is optionally clamped accordingly. + \imageMacro{QmitkSegmentationUtilities_ImageMasking.png,"Image masking",6.00} \section org_mitk_views_segmentationUtilitiesMorphologicalOperations Morphological operations Morphological operations are applied to a single segmentation image. Based on a given structuring element the underlying segmentation will be modfied. The plugin provides a <b>ball</b> and a <b>cross</b> as structuring elements. The follow operations are available: <ul> <li> <b>Dilation:</b> Each labeled pixel within the segmentation will be dilated based on the selected structuring element. <li> <b>Erosion:</b> Each labeled pixel within the segmentation will be eroded based on the selected structuring element. <li> <b>Opening:</b> A dilation followed by an erosion, used for smoothing edges or eliminating small objects. <li> <b>Closing:</b> An erosion followed by an dilation, used for filling small holes. <li> <b>Fill Holes:</b> Fills bigger holes within a segmentation. </ul> \imageMacro{QmitkSegmentationUtilities_MorphologicalOperations.png,"Morphological operations",6.00} \section org_mitk_views_segmentationUtilitiesSurfaceToImage Surface to image Surface to image allows to create a segmentation out of a given surface. The operation requires a surface and a reference image. The created segmentation image will have the same geometrical properties like the reference image (dimension, size and Geometry3D). \imageMacro{QmitkSegmentationUtilities_SurfaceToImage.png,"Surface to image",6.00} **/ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities_ImageMasking.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities_ImageMasking.png index 5664184a23..1a28460a18 100644 Binary files a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities_ImageMasking.png and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentationUtilities_ImageMasking.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp index e1818bc9b9..0ee6636348 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp @@ -1,291 +1,356 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageMaskingWidget.h" #include "mitkImage.h" #include "../../Common/QmitkDataSelectionWidget.h" #include <mitkException.h> #include <mitkExceptionMacro.h> #include <mitkImageStatisticsHolder.h> #include <mitkMaskImageFilter.h> #include <mitkProgressBar.h> #include <mitkSliceNavigationController.h> #include <mitkSurfaceToImageFilter.h> +#include <mitkImageAccessByItk.h> #include <QMessageBox> +#include <limits> + namespace { bool IsSurface(const mitk::DataNode* dataNode) { if (nullptr != dataNode) { if (nullptr != dynamic_cast<const mitk::Surface*>(dataNode->GetData())) return true; } return false; } } static const char* const HelpText = "Select an image and a segmentation or surface"; QmitkImageMaskingWidget::QmitkImageMaskingWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent) : QmitkSegmentationUtilityWidget(timeNavigationController, parent) { m_Controls.setupUi(this); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ImagePredicate); m_Controls.dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); m_Controls.dataSelectionWidget->SetHelpText(HelpText); this->EnableButtons(false); - connect (m_Controls.btnMaskImage, SIGNAL(clicked()), this, SLOT(OnMaskImagePressed())); - + connect(m_Controls.btnMaskImage, SIGNAL(clicked()), this, SLOT(OnMaskImagePressed())); + connect(m_Controls.rbnCustom, SIGNAL(toggled(bool)), this, SLOT(OnCustomValueButtonToggled(bool))); connect(m_Controls.dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); if( m_Controls.dataSelectionWidget->GetSelection(0).IsNotNull() && m_Controls.dataSelectionWidget->GetSelection(1).IsNotNull() ) { this->OnSelectionChanged(0, m_Controls.dataSelectionWidget->GetSelection(0)); } } QmitkImageMaskingWidget::~QmitkImageMaskingWidget() { } void QmitkImageMaskingWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode* selection) { QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node0 = dataSelectionWidget->GetSelection(0); mitk::DataNode::Pointer node1 = dataSelectionWidget->GetSelection(1); if (node0.IsNull() || node1.IsNull() ) { dataSelectionWidget->SetHelpText(HelpText); this->EnableButtons(false); } else { this->SelectionControl(index, selection); } } void QmitkImageMaskingWidget::SelectionControl(unsigned int index, const mitk::DataNode* selection) { QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; mitk::DataNode::Pointer node = dataSelectionWidget->GetSelection(index); //if Image-Masking is enabled, check if image-dimension of reference and binary image is identical if( !IsSurface(dataSelectionWidget->GetSelection(1)) ) { if( dataSelectionWidget->GetSelection(0) == dataSelectionWidget->GetSelection(1) ) { dataSelectionWidget->SetHelpText("Select two different images above"); this->EnableButtons(false); return; } else if( node.IsNotNull() && selection ) { mitk::Image::Pointer referenceImage = dynamic_cast<mitk::Image*> ( dataSelectionWidget->GetSelection(0)->GetData() ); mitk::Image::Pointer maskImage = dynamic_cast<mitk::Image*> ( dataSelectionWidget->GetSelection(1)->GetData() ); if( maskImage.IsNull() || referenceImage->GetLargestPossibleRegion().GetSize() != maskImage->GetLargestPossibleRegion().GetSize() ) { dataSelectionWidget->SetHelpText("Different image sizes cannot be masked"); this->EnableButtons(false); return; } } else { dataSelectionWidget->SetHelpText(HelpText); return; } } dataSelectionWidget->SetHelpText(""); this->EnableButtons(); } void QmitkImageMaskingWidget::EnableButtons(bool enable) { + m_Controls.grpBackgroundValue->setEnabled(enable); m_Controls.btnMaskImage->setEnabled(enable); } +template<typename TPixel, unsigned int VImageDimension> +void GetRange(const itk::Image<TPixel, VImageDimension>*, double& bottom, double& top) +{ + bottom = std::numeric_limits<TPixel>::lowest(); + top = std::numeric_limits<TPixel>::max(); +} + +void QmitkImageMaskingWidget::OnCustomValueButtonToggled(bool checked) +{ + m_Controls.txtCustom->setEnabled(checked); +} + void QmitkImageMaskingWidget::OnMaskImagePressed() { //Disable Buttons during calculation and initialize Progressbar this->EnableButtons(false); mitk::ProgressBar::GetInstance()->AddStepsToDo(4); mitk::ProgressBar::GetInstance()->Progress(); QmitkDataSelectionWidget* dataSelectionWidget = m_Controls.dataSelectionWidget; //create result image, get mask node and reference image mitk::Image::Pointer resultImage(nullptr); mitk::DataNode::Pointer maskingNode = dataSelectionWidget->GetSelection(1); mitk::Image::Pointer referenceImage = static_cast<mitk::Image*>(dataSelectionWidget->GetSelection(0)->GetData()); if(referenceImage.IsNull() || maskingNode.IsNull() ) { MITK_ERROR << "Selection does not contain an image"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain an image", QMessageBox::Ok ); m_Controls.btnMaskImage->setEnabled(true); return; } //Do Image-Masking if (!IsSurface(maskingNode)) { mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer maskImage = dynamic_cast<mitk::Image*> ( maskingNode->GetData() ); if(maskImage.IsNull() ) { MITK_ERROR << "Selection does not contain a segmentation"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a segmentation", QMessageBox::Ok ); this->EnableButtons(); return; } if( referenceImage->GetLargestPossibleRegion().GetSize() == maskImage->GetLargestPossibleRegion().GetSize() ) { resultImage = this->MaskImage( referenceImage, maskImage ); } - } //Do Surface-Masking else { mitk::ProgressBar::GetInstance()->Progress(); //1. convert surface to image mitk::Surface::Pointer surface = dynamic_cast<mitk::Surface*> ( maskingNode->GetData() ); //TODO Get 3D Surface of current time step if(surface.IsNull()) { MITK_ERROR << "Selection does not contain a surface"; QMessageBox::information( this, "Image and Surface Masking", "Selection does not contain a surface", QMessageBox::Ok ); this->EnableButtons(); return; } mitk::Image::Pointer maskImage = this->ConvertSurfaceToImage( referenceImage, surface ); //2. mask reference image with mask image if(maskImage.IsNotNull() && referenceImage->GetLargestPossibleRegion().GetSize() == maskImage->GetLargestPossibleRegion().GetSize() ) { resultImage = this->MaskImage( referenceImage, maskImage ); } } mitk::ProgressBar::GetInstance()->Progress(); if( resultImage.IsNull() ) { MITK_ERROR << "Masking failed"; QMessageBox::information( this, "Image and Surface Masking", "Masking failed. For more information please see logging window.", QMessageBox::Ok ); this->EnableButtons(); mitk::ProgressBar::GetInstance()->Progress(4); return; } //Add result to data storage this->AddToDataStorage( dataSelectionWidget->GetDataStorage(), resultImage, dataSelectionWidget->GetSelection(0)->GetName() + "_" + dataSelectionWidget->GetSelection(1)->GetName(), dataSelectionWidget->GetSelection(0)); this->EnableButtons(); mitk::ProgressBar::GetInstance()->Progress(); } mitk::Image::Pointer QmitkImageMaskingWidget::MaskImage(mitk::Image::Pointer referenceImage, mitk::Image::Pointer maskImage ) { - mitk::Image::Pointer resultImage(nullptr); + mitk::ScalarType backgroundValue = 0.0; + + if (m_Controls.rbnMinimum->isChecked()) + { + backgroundValue = referenceImage->GetStatistics()->GetScalarValueMin(); + } + else if (m_Controls.rbnCustom->isChecked()) + { + auto warningTitle = QStringLiteral("Invalid custom pixel value"); + + bool ok = false; + auto originalBackgroundValue = m_Controls.txtCustom->text().toDouble(&ok); - mitk::MaskImageFilter::Pointer maskFilter = mitk::MaskImageFilter::New(); - maskFilter->SetInput( referenceImage ); - maskFilter->SetMask( maskImage ); + if (!ok) + { + // Input is not even a number + QMessageBox::warning(nullptr, warningTitle, "Please enter a valid number as custom pixel value."); + return nullptr; + } + else + { + // Clamp to the numerical limits of the pixel/component type + double bottom, top; + AccessByItk_n(referenceImage, GetRange, (bottom, top)); + backgroundValue = std::max(bottom, std::min(originalBackgroundValue, top)); + + // Get rid of decimals for integral numbers + auto type = referenceImage->GetPixelType().GetComponentType(); + if (type != itk::ImageIOBase::FLOAT && type != itk::ImageIOBase::DOUBLE) + backgroundValue = std::round(backgroundValue); + } + + // Ask the user for permission before correcting their input + if (std::abs(originalBackgroundValue - backgroundValue) > 1e-4) + { + auto warningText = QString( + "<p>The custom pixel value <b>%1</b> lies not within the range of valid pixel values for the selected image.</p>" + "<p>Apply the closest valid pixel value <b>%2</b> instead?</p>").arg(originalBackgroundValue).arg(backgroundValue); + + auto ret = QMessageBox::warning( + nullptr, + warningTitle, + warningText, + QMessageBox::StandardButton::Apply | QMessageBox::StandardButton::Cancel, + QMessageBox::StandardButton::Apply); + + if (QMessageBox::StandardButton::Apply != ret) + return nullptr; + + m_Controls.txtCustom->setText(QString("%1").arg(backgroundValue)); + } + } + + auto maskFilter = mitk::MaskImageFilter::New(); + maskFilter->SetInput(referenceImage); + maskFilter->SetMask(maskImage); maskFilter->OverrideOutsideValueOn(); - maskFilter->SetOutsideValue( referenceImage->GetStatistics()->GetScalarValueMin() ); + maskFilter->SetOutsideValue(backgroundValue); + try { maskFilter->Update(); } - catch(itk::ExceptionObject& excpt) + catch(const itk::ExceptionObject& e) { - MITK_ERROR << excpt.GetDescription(); + MITK_ERROR << e.GetDescription(); return nullptr; } - resultImage = maskFilter->GetOutput(); - - return resultImage; + return maskFilter->GetOutput(); } mitk::Image::Pointer QmitkImageMaskingWidget::ConvertSurfaceToImage( mitk::Image::Pointer image, mitk::Surface::Pointer surface ) { mitk::ProgressBar::GetInstance()->AddStepsToDo(2); mitk::ProgressBar::GetInstance()->Progress(); mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(surface); surfaceToImageFilter->SetImage(image); try { surfaceToImageFilter->Update(); } catch(itk::ExceptionObject& excpt) { MITK_ERROR << excpt.GetDescription(); return nullptr; } mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer resultImage = mitk::Image::New(); resultImage = surfaceToImageFilter->GetOutput(); return resultImage; } void QmitkImageMaskingWidget::AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent ) { auto dataNode = mitk::DataNode::New(); dataNode->SetName(name); dataNode->SetData(segmentation); if (parent.IsNotNull()) { mitk::LevelWindow levelWindow; parent->GetLevelWindow(levelWindow); dataNode->SetLevelWindow(levelWindow); } dataStorage->Add(dataNode, parent); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h index 37a5711983..7a24096fb5 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h @@ -1,74 +1,77 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkImageMaskingWidget_h #define QmitkImageMaskingWidget_h #include "../QmitkSegmentationUtilityWidget.h" #include <ui_QmitkImageMaskingWidgetControls.h> #include <mitkSurface.h> namespace mitk { class Image; } /*! \brief QmitkImageMaskingWidget Tool masks an image with a binary image or a surface. The Method requires an image and a binary image mask or a surface. The input image and the binary image mask must be of the same size. Masking with a surface creates first a binary image of the surface and then use this for the masking of the input image. */ class QmitkImageMaskingWidget : public QmitkSegmentationUtilityWidget { Q_OBJECT public: /** @brief Default constructor, including creation of GUI elements and signals/slots connections. */ explicit QmitkImageMaskingWidget(mitk::SliceNavigationController* timeNavigationController, QWidget* parent = nullptr); /** @brief Defaul destructor. */ ~QmitkImageMaskingWidget() override; private slots: /** @brief This slot is called if the selection in the workbench is changed. */ void OnSelectionChanged(unsigned int index, const mitk::DataNode* selection); /** @brief This slot is called if user activates the button to mask an image. */ void OnMaskImagePressed(); + /** @brief This slot is called if the user toggles the "Custom" radio button. */ + void OnCustomValueButtonToggled(bool checked); + private: /** @brief Check if selections is valid. */ void SelectionControl( unsigned int index, const mitk::DataNode* selection); /** @brief Enable buttons if data selction is valid. */ void EnableButtons(bool enable = true); /** @brief Mask an image with a given binary mask. Note that the input image and the mask image must be of the same size. */ itk::SmartPointer<mitk::Image> MaskImage(itk::SmartPointer<mitk::Image> referenceImage, itk::SmartPointer<mitk::Image> maskImage ); /** @brief Convert a surface into an binary image. */ itk::SmartPointer<mitk::Image> ConvertSurfaceToImage( itk::SmartPointer<mitk::Image> image, mitk::Surface::Pointer surface ); /** @brief Adds a new data object to the DataStorage.*/ void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, itk::SmartPointer<mitk::Image> segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr); Ui::QmitkImageMaskingWidgetControls m_Controls; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui index 7ff3b3184c..ff45a758c6 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui @@ -1,56 +1,109 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>QmitkImageMaskingWidgetControls</class> <widget class="QWidget" name="QmitkImageMaskingWidgetControls"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>147</width> - <height>155</height> + <width>238</width> + <height>329</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QmitkDataSelectionWidget" name="dataSelectionWidget" native="true"> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> </widget> </item> + <item> + <widget class="QGroupBox" name="grpBackgroundValue"> + <property name="title"> + <string>Background value</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QRadioButton" name="rbnZero"> + <property name="text"> + <string>Zero</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="rbnMinimum"> + <property name="text"> + <string>Minimum</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="rbnCustom"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Custom:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="txtCustom"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> <item> <widget class="QPushButton" name="btnMaskImage"> <property name="text"> <string>Mask</string> </property> </widget> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>40</height> </size> </property> </spacer> </item> </layout> </widget> <customwidgets> <customwidget> <class>QmitkDataSelectionWidget</class> <extends>QWidget</extends> <header>internal/Common/QmitkDataSelectionWidget.h</header> <container>1</container> </customwidget> </customwidgets> <resources/> <connections/> </ui>