diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp index 884c5a79cd..78c4d2450b 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp @@ -1,389 +1,288 @@ /*============================================================================ 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 #include #include #include #include #include #include -#include #include #include -#include #include #include +#include +#include +#include +#include #include #include namespace { - bool IsSurface(const mitk::DataNode* dataNode) + mitk::NodePredicateBase::Pointer GetInputPredicate() { - if (nullptr != dataNode) - { - if (nullptr != dynamic_cast(dataNode->GetData())) - return true; - } + auto isImage = mitk::TNodePredicateDataType::New(); + auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); - return false; + auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isImage); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + return isValidInput.GetPointer(); } } static const char* const HelpText = "Select an image and a segmentation or surface"; QmitkImageMaskingWidget::QmitkImageMaskingWidget(mitk::DataStorage* dataStorage, QWidget* parent) - : QWidget(parent) + : QWidget(parent), m_DataStorage(dataStorage) { m_Controls = new Ui::QmitkImageMaskingWidgetControls; m_Controls->setupUi(this); - m_Controls->dataSelectionWidget->SetDataStorage(dataStorage); - m_Controls->dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::ImagePredicate); - m_Controls->dataSelectionWidget->AddDataSelection(QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); - m_Controls->dataSelectionWidget->SetHelpText(HelpText); + m_Controls->imageNodeSelector->SetDataStorage(dataStorage); + m_Controls->imageNodeSelector->SetNodePredicate(GetInputPredicate()); + m_Controls->imageNodeSelector->SetSelectionIsOptional(false); + m_Controls->imageNodeSelector->SetInvalidInfo(QStringLiteral("Please select image for masking")); + m_Controls->imageNodeSelector->SetPopUpTitel(QStringLiteral("Select image")); + m_Controls->imageNodeSelector->SetPopUpHint(QStringLiteral("Select an image that you want to mask.")); - // T28795: Disable 2-d reference images since they do not work yet (segmentations are at least 3-d images with a single slice) - m_Controls->dataSelectionWidget->SetPredicate(0, mitk::NodePredicateAnd::New( - mitk::NodePredicateNot::New(mitk::NodePredicateDimension::New(2)), - m_Controls->dataSelectionWidget->GetPredicate(0))); + m_Controls->segNodeSelector->SetDataStorage(dataStorage); + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); + m_Controls->segNodeSelector->SetSelectionIsOptional(false); + m_Controls->segNodeSelector->SetInvalidInfo(QStringLiteral("Please select segmentation and its label")); + m_Controls->segNodeSelector->SetPopUpTitel(QStringLiteral("Select segmentation")); + m_Controls->segNodeSelector->SetPopUpHint(QStringLiteral("Select the segmentation that should be used for masking.\nSegmentation must have the same geometry like the image that should be masked.")); - this->EnableButtons(false); + this->ConfigureWidgets(); - 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*))); + connect(m_Controls->imageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkImageMaskingWidget::OnImageSelectionChanged); + connect(m_Controls->segNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkImageMaskingWidget::OnSegSelectionChanged); - if( m_Controls->dataSelectionWidget->GetSelection(0).IsNotNull() && - m_Controls->dataSelectionWidget->GetSelection(1).IsNotNull() ) - { - this->OnSelectionChanged(0, m_Controls->dataSelectionWidget->GetSelection(0)); - } + connect(m_Controls->btnMaskImage, &QPushButton::clicked, this, &QmitkImageMaskingWidget::OnMaskImagePressed); + connect(m_Controls->rbnCustom, &QRadioButton::toggled, this, &QmitkImageMaskingWidget::OnCustomValueButtonToggled); + + m_Controls->imageNodeSelector->SetAutoSelectNewNodes(true); + m_Controls->segNodeSelector->SetAutoSelectNewNodes(true); } QmitkImageMaskingWidget::~QmitkImageMaskingWidget() { + m_Controls->labelInspector->SetMultiLabelNode(nullptr); } -void QmitkImageMaskingWidget::OnSelectionChanged(unsigned int index, const mitk::DataNode *selection) +void QmitkImageMaskingWidget::OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { - auto *dataSelectionWidget = m_Controls->dataSelectionWidget; - auto node0 = dataSelectionWidget->GetSelection(0); - - if (index == 0) + auto imageNode = m_Controls->imageNodeSelector->GetSelectedNode(); + const mitk::BaseGeometry* refGeometry = nullptr; + const mitk::BaseData* inputImage = (nullptr != imageNode) ? imageNode->GetData() : nullptr; + if (nullptr != inputImage) { - dataSelectionWidget->SetPredicate(1, QmitkDataSelectionWidget::SegmentationOrSurfacePredicate); - - if (node0.IsNotNull()) - { - dataSelectionWidget->SetPredicate(1, mitk::NodePredicateAnd::New( - mitk::NodePredicateGeometry::New(node0->GetData()->GetGeometry()), - dataSelectionWidget->GetPredicate(1))); - } + refGeometry = inputImage->GetGeometry(); } - auto node1 = dataSelectionWidget->GetSelection(1); - - if (node0.IsNull() || node1.IsNull()) - { - dataSelectionWidget->SetHelpText(HelpText); - this->EnableButtons(false); - } - else - { - this->SelectionControl(index, selection); - } + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate(refGeometry)); + this->ConfigureWidgets(); } -void QmitkImageMaskingWidget::SelectionControl(unsigned int index, const mitk::DataNode* selection) +void QmitkImageMaskingWidget::OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { - 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)) ) + auto node = m_Controls->segNodeSelector->GetSelectedNode(); + m_Controls->labelInspector->SetMultiLabelNode(node); + if (node.IsNotNull()) { - if( dataSelectionWidget->GetSelection(0) == dataSelectionWidget->GetSelection(1) ) + auto labelValues = m_Controls->labelInspector->GetMultiLabelSegmentation()->GetAllLabelValues(); + if (!labelValues.empty()) { - dataSelectionWidget->SetHelpText("Select two different images above"); - this->EnableButtons(false); - return; + m_Controls->labelInspector->SetSelectedLabel(labelValues.front()); } + } + this->ConfigureWidgets(); +} - 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()) - { - dataSelectionWidget->SetHelpText("Different image sizes cannot be masked"); - this->EnableButtons(false); - return; - } - } +void QmitkImageMaskingWidget::ConfigureWidgets() +{ + auto iNode = m_Controls->imageNodeSelector->GetSelectedNode(); + auto sNode = m_Controls->segNodeSelector->GetSelectedNode(); - else - { - dataSelectionWidget->SetHelpText(HelpText); - return; - } - } + bool enable = iNode.IsNotNull() && sNode.IsNotNull() && !m_Controls->labelInspector->GetSelectedLabels().empty(); - dataSelectionWidget->SetHelpText(""); - this->EnableButtons(); + this->EnableButtons(enable); } 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; - } + mitk::LabelSetImage::Pointer segmentation = m_Controls->labelInspector->GetMultiLabelSegmentation(); + mitk::Image::Pointer referenceImage = static_cast(m_Controls->imageNodeSelector->GetSelectedNode()->GetData()); - //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; - } - - 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::ProgressBar::GetInstance()->Progress(); - 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 ); - } - } + auto labelImage = mitk::CreateLabelMask(segmentation, m_Controls->labelInspector->GetSelectedLabels().front(), true); + resultImage = this->MaskImage(referenceImage, labelImage); 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(); + QMessageBox::information( this, "Image Masking", "Masking failed. For more information please see logging window.", QMessageBox::Ok ); + this->EnableButtons(true); mitk::ProgressBar::GetInstance()->Progress(4); return; } //Add result to data storage - this->AddToDataStorage( - dataSelectionWidget->GetDataStorage(), + this->AddToDataStorage(m_DataStorage.Lock(), resultImage, - dataSelectionWidget->GetSelection(0)->GetName() + "_" + dataSelectionWidget->GetSelection(1)->GetName(), - dataSelectionWidget->GetSelection(0)); + m_Controls->imageNodeSelector->GetSelectedNode()->GetName() + "_" + m_Controls->segNodeSelector->GetSelectedNode()->GetName(), + m_Controls->imageNodeSelector->GetSelectedNode()); - this->EnableButtons(); + this->EnableButtons(true); mitk::ProgressBar::GetInstance()->Progress(); } mitk::Image::Pointer QmitkImageMaskingWidget::MaskImage(mitk::Image::Pointer referenceImage, mitk::Image::Pointer maskImage ) { 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); 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; if (referenceImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(referenceImage, GetRange, 4, (bottom, top)); } else { 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::IOComponentEnum::FLOAT && type != itk::IOComponentEnum::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(backgroundValue); try { maskFilter->Update(); } catch(const itk::ExceptionObject& e) { MITK_ERROR << e.GetDescription(); return nullptr; } 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 ) { if (dataStorage.IsNull()) { std::string exception = "Cannot add result to the data storage. Data storage invalid."; MITK_ERROR << "Masking failed: " << exception; QMessageBox::information(nullptr, "Masking failed", QString::fromStdString(exception)); } 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/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h index 3f82fcd570..4c4fc1a9be 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.h @@ -1,85 +1,83 @@ /*============================================================================ 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 #include -#include +#include #include namespace Ui { class QmitkImageMaskingWidgetControls; } namespace mitk { class Image; + class DataStorage; } /*! \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 MITKSEGMENTATIONUI_EXPORT QmitkImageMaskingWidget : public QWidget { Q_OBJECT public: /** @brief Default constructor, including creation of GUI elements and signals/slots connections. */ explicit QmitkImageMaskingWidget(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); /** @brief Default destructor. */ ~QmitkImageMaskingWidget() override; -private slots: +private: + + /** @brief This slot is called if the image selection changed.*/ + void OnImageSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/); - /** @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 the segmentation selection changed.*/ + void OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/); /** @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 selection is valid. */ - void EnableButtons(bool enable = true); + /** @brief Configure the widgets according to the internal state. */ + void ConfigureWidgets(); + void EnableButtons(bool enable); /** @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); + mitk::WeakPointer m_DataStorage; Ui::QmitkImageMaskingWidgetControls* m_Controls; }; #endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui index 6d0bfa0eaf..0d929be21e 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidgetControls.ui @@ -1,109 +1,114 @@ QmitkImageMaskingWidgetControls 0 0 238 329 - - - - 0 - 0 - - - + + + + + + + Background value Zero true Minimum 0 0 Custom: false 0 Mask Qt::Vertical 20 40 - QmitkDataSelectionWidget + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+ 1 +
+ + QmitkMultiLabelInspector QWidget -
QmitkDataSelectionWidget.h
+
QmitkMultiLabelInspector.h
1