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 Segmentation Utilities View allows to postprocess existing segmentations. Currently five different operations exist:
- \ref org_mitk_views_segmentationUtilitiesBooleanOperations
- \ref org_mitk_views_segmentationUtilitiesContourToImage
- \ref org_mitk_views_segmentationUtilitiesImageMasking
- \ref org_mitk_views_segmentationUtilitiesMorphologicalOperations
- \ref org_mitk_views_segmentationUtilitiesSurfaceToImage
\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:
- Difference: Subtracts the second segmentation from the first segmentation.
- Intersection: Extracts the overlapping areas of the two selected segmentations.
- Union: Combines the two existing segmentations.
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 ball and a cross as structuring elements. The follow operations are available:
- Dilation: Each labeled pixel within the segmentation will be dilated based on the selected structuring element.
- Erosion: Each labeled pixel within the segmentation will be eroded based on the selected structuring element.
- Opening: A dilation followed by an erosion, used for smoothing edges or eliminating small objects.
- Closing: An erosion followed by an dilation, used for filling small holes.
- Fill Holes: Fills bigger holes within a segmentation.
\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
#include
#include
#include
#include
#include
#include
+#include
#include
+#include
+
namespace
{
bool IsSurface(const mitk::DataNode* dataNode)
{
if (nullptr != dataNode)
{
if (nullptr != dynamic_cast(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 ( dataSelectionWidget->GetSelection(0)->GetData() );
mitk::Image::Pointer maskImage = dynamic_cast ( 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
+void GetRange(const itk::Image*, double& bottom, double& top)
+{
+ bottom = std::numeric_limits::lowest();
+ top = std::numeric_limits::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(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 ( 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 ( 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(
+ "The custom pixel value %1 lies not within the range of valid pixel values for the selected image.
"
+ "Apply the closest valid pixel value %2 instead?
").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
#include
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 MaskImage(itk::SmartPointer referenceImage, itk::SmartPointer maskImage );
/** @brief Convert a surface into an binary image. */
itk::SmartPointer ConvertSurfaceToImage( itk::SmartPointer image, mitk::Surface::Pointer surface );
/** @brief Adds a new data object to the DataStorage.*/
void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, itk::SmartPointer 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 @@
QmitkImageMaskingWidgetControls
0
0
- 147
- 155
+ 238
+ 329
-
0
0
+ -
+
+
+ Background value
+
+
+
-
+
+
+ Zero
+
+
+ true
+
+
+
+ -
+
+
+ Minimum
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Custom:
+
+
+
+ -
+
+
+ false
+
+
+ 0
+
+
+
+
+
+
+
+
-
Mask
-
Qt::Vertical
20
40
QmitkDataSelectionWidget
QWidget
internal/Common/QmitkDataSelectionWidget.h
1