diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp index 58d982de3f..db30798301 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.cpp @@ -1,157 +1,233 @@ /*============================================================================ 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 "QmitkBooleanOperationsWidget.h" #include <ui_QmitkBooleanOperationsWidgetControls.h> #include <mitkDataStorage.h> #include <mitkException.h> #include <mitkRenderingManager.h> -#include <mitkTimeNavigationController.h> +#include <mitkMultiLabelPredicateHelper.h> +#include <mitkBooleanOperation.h> +#include <mitkProgressBar.h> +#include <mitkLabelSetImageHelper.h> -#include <QMessageBox> - -#include <cassert> - -static const char* const HelpText = "Select two different segmentations above"; - -namespace -{ - static std::string GetPrefix(mitk::BooleanOperation::Type type) - { - switch (type) - { - case mitk::BooleanOperation::Difference: - return "DifferenceFrom_"; - - case mitk::BooleanOperation::Intersection: - return "IntersectionWith_"; - - case mitk::BooleanOperation::Union: - return "UnionWith_"; - - default: - assert(false && "Unknown boolean operation type"); - return "UNKNOWN_BOOLEAN_OPERATION_WITH_"; - } - } - - static void AddToDataStorage(mitk::DataStorage::Pointer dataStorage, mitk::Image::Pointer segmentation, const std::string& name, mitk::DataNode::Pointer parent = nullptr) - { - if (dataStorage.IsNull()) - { - std::string exception = "Cannot add result to the data storage. Data storage invalid."; - MITK_ERROR << "Boolean operation failed: " << exception; - QMessageBox::information(nullptr, "Boolean operation failed", QString::fromStdString(exception)); - } - - auto dataNode = mitk::DataNode::New(); - dataNode->SetName(name); - dataNode->SetData(segmentation); - - dataStorage->Add(dataNode, parent); - } -} QmitkBooleanOperationsWidget::QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent) : QWidget(parent) { m_Controls = new Ui::QmitkBooleanOperationsWidgetControls; m_Controls->setupUi(this); - m_Controls->dataSelectionWidget->SetDataStorage(dataStorage); - m_Controls->dataSelectionWidget->AddDataSelection("<img width=16 height=16 src=\":/Qmitk/BooleanLabelA_32x32.png\"/>", "Select 1st segmentation", "Select 1st segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); - m_Controls->dataSelectionWidget->AddDataSelection("<img width=16 height=16 src=\":/Qmitk/BooleanLabelB_32x32.png\"/>", "Select 2nd segmentation", "Select 2nd segmentation", "", QmitkDataSelectionWidget::SegmentationPredicate); + m_Controls->label1st->setText("<img width=16 height=16 src=\":/Qmitk/BooleanLabelA_32x32.png\"/>"); + m_Controls->label1st->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + m_Controls->label2nd->setText("<img width=16 height=16 src=\":/Qmitk/BooleanLabelB_32x32.png\"/>"); + m_Controls->label2nd->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - m_Controls->dataSelectionWidget->SetHelpText(HelpText); + m_Controls->segNodeSelector->SetDataStorage(dataStorage); + m_Controls->segNodeSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); + m_Controls->segNodeSelector->SetSelectionIsOptional(false); + m_Controls->segNodeSelector->SetInvalidInfo(QStringLiteral("Please select segmentation for extraction.")); + m_Controls->segNodeSelector->SetPopUpTitel(QStringLiteral("Select segmentation")); + m_Controls->segNodeSelector->SetPopUpHint(QStringLiteral("Select the segmentation that should be used as source for extraction.")); + + m_Controls->labelInspector->SetMultiSelectionMode(true); + + connect(m_Controls->segNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, + this, &QmitkBooleanOperationsWidget::OnSegSelectionChanged); + + connect(m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, + this, &QmitkBooleanOperationsWidget::OnLabelSelectionChanged); - connect(m_Controls->dataSelectionWidget, SIGNAL(SelectionChanged(unsigned int, const mitk::DataNode*)), this, SLOT(OnSelectionChanged(unsigned int, const mitk::DataNode*))); connect(m_Controls->differenceButton, SIGNAL(clicked()), this, SLOT(OnDifferenceButtonClicked())); connect(m_Controls->intersectionButton, SIGNAL(clicked()), this, SLOT(OnIntersectionButtonClicked())); connect(m_Controls->unionButton, SIGNAL(clicked()), this, SLOT(OnUnionButtonClicked())); + + m_Controls->segNodeSelector->SetAutoSelectNewNodes(true); } QmitkBooleanOperationsWidget::~QmitkBooleanOperationsWidget() { + m_Controls->labelInspector->SetMultiLabelNode(nullptr); } -void QmitkBooleanOperationsWidget::OnSelectionChanged(unsigned int, const mitk::DataNode*) +void QmitkBooleanOperationsWidget::OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { - auto dataSelectionWidget = m_Controls->dataSelectionWidget; + auto node = m_Controls->segNodeSelector->GetSelectedNode(); + m_Controls->labelInspector->SetMultiLabelNode(node); + + this->ConfigureWidgets(); +} + +void QmitkBooleanOperationsWidget::OnLabelSelectionChanged(mitk::LabelSetImage::LabelValueVectorType labels) +{ + this->ConfigureWidgets(); +} - auto nodeA = dataSelectionWidget->GetSelection(0); - auto nodeB = dataSelectionWidget->GetSelection(1); +void QmitkBooleanOperationsWidget::ConfigureWidgets() +{ + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); - if (nodeA.IsNotNull() && nodeB.IsNotNull() && nodeA != nodeB) + if (selectedLabelValues.empty()) { - dataSelectionWidget->SetHelpText(""); - this->EnableButtons(); + m_Controls->line1stLabel->setText(""); } else { - dataSelectionWidget->SetHelpText(HelpText); - this->EnableButtons(false); + m_Controls->line1stLabel->setText(QString::fromStdString(std::to_string(selectedLabelValues.front()))); } -} -void QmitkBooleanOperationsWidget::EnableButtons(bool enable) -{ - m_Controls->differenceButton->setEnabled(enable); - m_Controls->intersectionButton->setEnabled(enable); - m_Controls->unionButton->setEnabled(enable); + if (selectedLabelValues.size() < 2) + { + m_Controls->lineOtherLabels->setText(""); + } + else + { + std::stringstream stream; + for (mitk::LabelSetImage::LabelValueVectorType::iterator iter = selectedLabelValues.begin() + 1; iter != selectedLabelValues.end(); ++iter) + { + stream << *iter << "; "; + } + + m_Controls->lineOtherLabels->setText(QString::fromStdString(stream.str())); + } + + m_Controls->differenceButton->setEnabled(selectedLabelValues.size()>1); + m_Controls->intersectionButton->setEnabled(selectedLabelValues.size() > 1); + m_Controls->unionButton->setEnabled(selectedLabelValues.size() > 1); } void QmitkBooleanOperationsWidget::OnDifferenceButtonClicked() { - this->DoBooleanOperation(mitk::BooleanOperation::Difference); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; + + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; + + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); + auto minuend = selectedLabelValues.front(); + auto subtrahends = mitk::LabelSetImage::LabelValueVectorType(selectedLabelValues.begin() + 1, selectedLabelValues.end()); + + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + + auto resultMask = mitk::BooleanOperation::GenerateDifference(seg, minuend, subtrahends, progressCallback); + + std::stringstream name; + name << "Difference " << seg->GetLabel(minuend)->GetName() << " - "; + for (auto label : subtrahends) + { + name << " " << seg->GetLabel(label)->GetName(); + } + + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); } void QmitkBooleanOperationsWidget::OnIntersectionButtonClicked() { - this->DoBooleanOperation(mitk::BooleanOperation::Intersection); -} + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; -void QmitkBooleanOperationsWidget::OnUnionButtonClicked() -{ - this->DoBooleanOperation(mitk::BooleanOperation::Union); + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; + + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); + + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + + auto resultMask = mitk::BooleanOperation::GenerateIntersection(seg, selectedLabelValues, progressCallback); + + std::stringstream name; + name << "Intersection"; + for (auto label : selectedLabelValues) + { + name << " " << seg->GetLabel(label)->GetName(); + } + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); } -void QmitkBooleanOperationsWidget::DoBooleanOperation(mitk::BooleanOperation::Type type) +void QmitkBooleanOperationsWidget::OnUnionButtonClicked() { - const auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); - assert(timeNavigationController != nullptr); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + mitk::ProgressBar::GetInstance()->Reset(); + mitk::ProgressBar::GetInstance()->AddStepsToDo(110); + unsigned int currentProgress = 0; - mitk::Image::Pointer segmentationA = dynamic_cast<mitk::Image*>(m_Controls->dataSelectionWidget->GetSelection(0)->GetData()); - mitk::Image::Pointer segmentationB = dynamic_cast<mitk::Image*>(m_Controls->dataSelectionWidget->GetSelection(1)->GetData()); - mitk::Image::Pointer result; + auto progressCallback = [¤tProgress](float filterProgress) + { + auto delta = (filterProgress * 100) - currentProgress; + if (delta > 0) + { + currentProgress += delta; + mitk::ProgressBar::GetInstance()->Progress(delta); + } + }; - try - { - mitk::BooleanOperation booleanOperation(type, segmentationA, segmentationB, timeNavigationController->GetSelectedTimePoint()); - result = booleanOperation.GetResult(); + auto selectedLabelValues = m_Controls->labelInspector->GetSelectedLabels(); - assert(result.IsNotNull()); + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); - auto dataSelectionWidget = m_Controls->dataSelectionWidget; + auto resultMask = mitk::BooleanOperation::GenerateUnion(seg, selectedLabelValues, progressCallback); - AddToDataStorage( - dataSelectionWidget->GetDataStorage(), - result, - GetPrefix(type) + dataSelectionWidget->GetSelection(1)->GetName(), - dataSelectionWidget->GetSelection(0)); - } - catch (const mitk::Exception& exception) + std::stringstream name; + name << "Union"; + for (auto label : selectedLabelValues) { - MITK_ERROR << "Boolean operation failed: " << exception.GetDescription(); - QMessageBox::information(nullptr, "Boolean operation failed", exception.GetDescription()); + name << " " << seg->GetLabel(label)->GetName(); } + + this->SaveResultLabelMask(resultMask, name.str()); + + mitk::ProgressBar::GetInstance()->Reset(); + QApplication::restoreOverrideCursor(); +} + +void QmitkBooleanOperationsWidget::SaveResultLabelMask(const mitk::Image* resultMask, const std::string& labelName) const +{ + auto seg = m_Controls->labelInspector->GetMultiLabelSegmentation(); + if (seg == nullptr) mitkThrow() << "Widget is in invalid state. Processing was triggered with no segmentation selected."; + + auto labels = m_Controls->labelInspector->GetSelectedLabels(); + if (labels.empty()) mitkThrow() << "Widget is in invalid state. Processing was triggered with no label selected."; + + auto groupID = seg->AddLayer(); + auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(seg, labelName, true); + seg->AddLabelWithContent(newLabel, resultMask, groupID, 1); + + m_Controls->labelInspector->GetMultiLabelSegmentation()->Modified(); + m_Controls->labelInspector->GetMultiLabelNode()->Modified(); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h index 5d09e3eec6..6abacbf534 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidget.h @@ -1,54 +1,58 @@ /*============================================================================ 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 QmitkBooleanOperationsWidget_h #define QmitkBooleanOperationsWidget_h -#include <MitkSegmentationUIExports.h> +#include <mitkLabelSetImage.h> -#include <mitkBooleanOperation.h> +#include <QmitkAbstractNodeSelectionWidget.h> #include <QWidget> +#include <MitkSegmentationUIExports.h> + namespace Ui { class QmitkBooleanOperationsWidgetControls; } namespace mitk { class DataNode; class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkBooleanOperationsWidget : public QWidget { Q_OBJECT public: explicit QmitkBooleanOperationsWidget(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); ~QmitkBooleanOperationsWidget() override; private slots: - void OnSelectionChanged(unsigned int index, const mitk::DataNode* selection); + void OnSegSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList nodes); + void OnLabelSelectionChanged(mitk::LabelSetImage::LabelValueVectorType labels); void OnDifferenceButtonClicked(); void OnIntersectionButtonClicked(); void OnUnionButtonClicked(); private: - void EnableButtons(bool enable = true); - void DoBooleanOperation(mitk::BooleanOperation::Type type); + void ConfigureWidgets(); + void SaveResultLabelMask(const mitk::Image* resultMask, const std::string& labelName) const; + Ui::QmitkBooleanOperationsWidgetControls* m_Controls; }; #endif diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui index 00a0389ab4..8b26bce4a2 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui @@ -1,181 +1,235 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>QmitkBooleanOperationsWidgetControls</class> <widget class="QWidget" name="QmitkBooleanOperationsWidgetControls"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>210</width> - <height>91</height> + <width>249</width> + <height>227</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> + <widget class="QmitkSingleNodeSelectionWidget" name="segNodeSelector" native="true"/> + </item> + <item> + <widget class="QmitkMultiLabelInspector" name="labelInspector" native="true"/> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Selected operands:</string> </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item row="1" column="0"> + <widget class="QLabel" name="label2nd"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label1st"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="line1stLabel"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="placeholderText"> + <string>select 1st label</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lineOtherLabels"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="placeholderText"> + <string>select label</string> + </property> + </widget> + </item> + </layout> </widget> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QToolButton" name="differenceButton"> <property name="enabled"> <bool>false</bool> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="toolTip"> <string>Subtracts first segmentation from the second one</string> </property> <property name="text"> <string>Difference</string> </property> <property name="icon"> - <iconset> - <normaloff>:/Qmitk/BooleanDifference_48x48.png</normaloff>:/Qmitk/BooleanDifference_48x48.png - </iconset> + <iconset resource="../resources/SegmentationUI.qrc"> + <normaloff>:/Qmitk/BooleanDifference_48x48.png</normaloff>:/Qmitk/BooleanDifference_48x48.png</iconset> </property> <property name="iconSize"> <size> <width>24</width> <height>24</height> </size> </property> <property name="checkable"> <bool>false</bool> </property> <property name="checked"> <bool>false</bool> </property> <property name="toolButtonStyle"> <enum>Qt::ToolButtonTextUnderIcon</enum> </property> <property name="arrowType"> <enum>Qt::NoArrow</enum> </property> </widget> </item> <item> <widget class="QToolButton" name="intersectionButton"> <property name="enabled"> <bool>false</bool> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="toolTip"> <string>Keeps just the overlapping areas of two the segmentations</string> </property> <property name="text"> <string>Intersection</string> </property> <property name="icon"> - <iconset> - <normaloff>:/Qmitk/BooleanIntersection_48x48.png</normaloff>:/Qmitk/BooleanIntersection_48x48.png - </iconset> + <iconset resource="../resources/SegmentationUI.qrc"> + <normaloff>:/Qmitk/BooleanIntersection_48x48.png</normaloff>:/Qmitk/BooleanIntersection_48x48.png</iconset> </property> <property name="iconSize"> <size> <width>24</width> <height>24</height> </size> </property> <property name="checkable"> <bool>false</bool> </property> <property name="checked"> <bool>false</bool> </property> <property name="toolButtonStyle"> <enum>Qt::ToolButtonTextUnderIcon</enum> </property> <property name="arrowType"> <enum>Qt::NoArrow</enum> </property> </widget> </item> <item> <widget class="QToolButton" name="unionButton"> <property name="enabled"> <bool>false</bool> </property> <property name="sizePolicy"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="toolTip"> <string>Combines the two segmentations</string> </property> <property name="text"> <string>Union</string> </property> <property name="icon"> - <iconset> - <normaloff>:/Qmitk/BooleanUnion_48x48.png</normaloff>:/Qmitk/BooleanUnion_48x48.png - </iconset> + <iconset resource="../resources/SegmentationUI.qrc"> + <normaloff>:/Qmitk/BooleanUnion_48x48.png</normaloff>:/Qmitk/BooleanUnion_48x48.png</iconset> </property> <property name="iconSize"> <size> <width>24</width> <height>24</height> </size> </property> <property name="checkable"> <bool>false</bool> </property> <property name="checked"> <bool>false</bool> </property> <property name="toolButtonStyle"> <enum>Qt::ToolButtonTextUnderIcon</enum> </property> <property name="arrowType"> <enum>Qt::NoArrow</enum> </property> </widget> </item> </layout> </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> + <class>QmitkSingleNodeSelectionWidget</class> + <extends>QWidget</extends> + <header location="global">QmitkSingleNodeSelectionWidget.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>QmitkMultiLabelInspector</class> <extends>QWidget</extends> - <header>QmitkDataSelectionWidget.h</header> + <header location="global">QmitkMultiLabelInspector.h</header> <container>1</container> </customwidget> </customwidgets> <resources> <include location="../resources/SegmentationUI.qrc"/> </resources> <connections/> </ui>