diff --git a/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h b/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h index be17583bbb..195fa6675b 100644 --- a/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h +++ b/Modules/AlgorithmsExt/include/mitkMaskAndCutRoiImageFilter.h @@ -1,77 +1,72 @@ /*============================================================================ 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 mitkMaskAndCutRoiImageFilter_h #define mitkMaskAndCutRoiImageFilter_h #include "MitkAlgorithmsExtExports.h" #include "mitkImageToImageFilter.h" #include "itkRegionOfInterestImageFilter.h" #include "mitkAutoCropImageFilter.h" #include "mitkBoundingObject.h" #include "mitkDataNode.h" #include "mitkMaskImageFilter.h" namespace mitk { /** \brief Cuts a region of interest (ROI) out of an image In the first step, this filter reduces the image region of the given ROI to a minimum. Using this region, a subvolume ist cut out of the given input image. The ROI is then used to mask the subvolume. Pixel inside the ROI will have their original value, pixel outside will be replaced by m_OutsideValue */ class MITKALGORITHMSEXT_EXPORT MaskAndCutRoiImageFilter : public ImageToImageFilter { typedef itk::Image<short, 3> ItkImageType; typedef itk::Image<unsigned char, 3> ItkMaskType; typedef itk::ImageRegion<3> RegionType; typedef itk::RegionOfInterestImageFilter<ItkImageType, ItkImageType> ROIFilterType; public: mitkClassMacro(MaskAndCutRoiImageFilter, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - itkGetMacro(MaxValue, mitk::ScalarType); - itkGetMacro(MinValue, mitk::ScalarType); void SetRegionOfInterest(mitk::BaseData *roi); // void SetRegionOfInterest(Image::Pointer image); // void SetRegionOfInterest(BoundingObject::Pointer boundingObject); // void SetRegionOfInterestByNode(mitk::DataNode::Pointer node); // temporary fix for bug # mitk::Image::Pointer GetOutput(); protected: MaskAndCutRoiImageFilter(); ~MaskAndCutRoiImageFilter() override; void GenerateData() override; ROIFilterType::Pointer m_RoiFilter; mitk::AutoCropImageFilter::Pointer m_CropFilter; mitk::MaskImageFilter::Pointer m_MaskFilter; // needed for temporary fix mitk::Image::Pointer m_outputImage; - mitk::ScalarType m_MaxValue; - mitk::ScalarType m_MinValue; - }; // class } // namespace #endif diff --git a/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h b/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h index ced7d4cb2b..9a8e1b1482 100644 --- a/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h +++ b/Modules/AlgorithmsExt/include/mitkMaskImageFilter.h @@ -1,111 +1,93 @@ /*============================================================================ 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 mitkMaskImageFilter_h #define mitkMaskImageFilter_h #include "MitkAlgorithmsExtExports.h" #include "mitkCommon.h" -#include "mitkImageTimeSelector.h" #include "mitkImageToImageFilter.h" #include "itkImage.h" namespace mitk { //##Documentation //## @brief //## @ingroup Process class MITKALGORITHMSEXT_EXPORT MaskImageFilter : public ImageToImageFilter { public: mitkClassMacro(MaskImageFilter, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void SetMask(const mitk::Image *mask); - const mitk::Image *GetMask() const; - - /** - * get/set the min Value of the original image in the masked area - **/ - itkGetMacro(MinValue, mitk::ScalarType); - itkSetMacro(MinValue, mitk::ScalarType); - - /** - * get/set the max Value of the original image in the masked area - **/ - itkGetMacro(MaxValue, mitk::ScalarType); - itkSetMacro(MaxValue, mitk::ScalarType); + void SetMask(const mitk::Image *mask); + const Image *GetMask() const; + Image* GetMask(); /** * This value is used as outside value. This only works * if OverrideOutsideValue is set to true. Default is 0. **/ itkSetMacro(OutsideValue, mitk::ScalarType); /** * This value is used as outside value. This only works * if OverrideOutsideValue is set to true. Default is 0. */ itkGetMacro(OutsideValue, mitk::ScalarType); /** * If OverrideOutsideValue is set to false, this minimum * of the pixel type of the output image is taken as outside * value. If set to true, the value set via SetOutsideValue is * used as background. */ itkSetMacro(OverrideOutsideValue, bool); /** * If OverrideOutsideValue is set to false, this minimum * of the pixel type of the output image is taken as outside * value. If set to true, the value set via SetOutsideValue is * used as background. */ itkGetMacro(OverrideOutsideValue, bool); itkBooleanMacro(OverrideOutsideValue); protected: MaskImageFilter(); ~MaskImageFilter() override; void GenerateInputRequestedRegion() override; void GenerateOutputInformation() override; void GenerateData() override; - template <typename TPixel, unsigned int VImageDimension> - void InternalComputeMask(itk::Image<TPixel, VImageDimension> *itkImage); - - mitk::Image::Pointer m_Mask; - mitk::ImageTimeSelector::Pointer m_InputTimeSelector; - mitk::ImageTimeSelector::Pointer m_MaskTimeSelector; - mitk::ImageTimeSelector::Pointer m_OutputTimeSelector; + template <typename TPixel1, unsigned int VImageDimension1, typename TPixel2, unsigned int VImageDimension2> + void InternalComputeMask(itk::Image<TPixel1, VImageDimension1>* itkInput, itk::Image<TPixel2, VImageDimension2>* itkMask); //##Description //## @brief Time when Header was last initialized itk::TimeStamp m_TimeOfHeaderInitialization; mitk::ScalarType m_OutsideValue; - mitk::ScalarType m_MinValue; - mitk::ScalarType m_MaxValue; + TimeStepType m_CurrentOutputTS; bool m_OverrideOutsideValue; }; } // namespace mitk #endif diff --git a/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp index 255d1316e1..eb3490c4a7 100644 --- a/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp +++ b/Modules/AlgorithmsExt/src/mitkMaskAndCutRoiImageFilter.cpp @@ -1,92 +1,90 @@ /*============================================================================ 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 "mitkMaskAndCutRoiImageFilter.h" #include "mitkBoundingObjectToSegmentationFilter.h" #include "mitkImageCast.h" mitk::MaskAndCutRoiImageFilter::MaskAndCutRoiImageFilter() { this->SetNumberOfRequiredInputs(2); m_CropFilter = mitk::AutoCropImageFilter::New(); m_RoiFilter = ROIFilterType::New(); m_MaskFilter = mitk::MaskImageFilter::New(); } mitk::MaskAndCutRoiImageFilter::~MaskAndCutRoiImageFilter() { } void mitk::MaskAndCutRoiImageFilter::SetRegionOfInterest(mitk::BaseData *roi) { mitk::Image::Pointer image = dynamic_cast<mitk::Image *>(roi); if (image.IsNotNull()) { this->SetInput(1, image); return; } mitk::BoundingObject::Pointer boundingObject = dynamic_cast<mitk::BoundingObject *>(roi); if (boundingObject.IsNotNull() && this->GetInput(0) != nullptr) { mitk::BoundingObjectToSegmentationFilter::Pointer filter = mitk::BoundingObjectToSegmentationFilter::New(); filter->SetBoundingObject(boundingObject); filter->SetInput(this->GetInput(0)); filter->Update(); this->SetInput(1, filter->GetOutput()); return; } } mitk::Image::Pointer mitk::MaskAndCutRoiImageFilter::GetOutput() { return m_outputImage; } void mitk::MaskAndCutRoiImageFilter::GenerateData() { mitk::Image::ConstPointer inputImage = this->GetInput(0); mitk::Image::ConstPointer maskImage = this->GetInput(1); // mitk::Image::Pointer outputImage = this->GetOutput(); // temporary fix for bug # m_outputImage = this->GetOutput(); ItkImageType::Pointer itkImage = ItkImageType::New(); mitk::Image::Pointer tmpImage = mitk::Image::New(); m_CropFilter->SetInput(maskImage); m_CropFilter->SetBackgroundValue(0); m_CropFilter->Update(); RegionType region = m_CropFilter->GetCroppingRegion(); mitk::CastToItkImage(inputImage, itkImage); m_RoiFilter->SetInput(itkImage); m_RoiFilter->SetRegionOfInterest(region); m_RoiFilter->Update(); mitk::CastToMitkImage(m_RoiFilter->GetOutput(), tmpImage); m_MaskFilter->SetInput(0, tmpImage); m_MaskFilter->SetMask(m_CropFilter->GetOutput()); m_MaskFilter->SetOutsideValue(-32765); m_MaskFilter->Update(); - m_MaxValue = m_MaskFilter->GetMaxValue(); - m_MinValue = m_MaskFilter->GetMinValue(); // temporary fix for bug # m_outputImage = m_MaskFilter->GetOutput(); m_outputImage->DisconnectPipeline(); } diff --git a/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp b/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp index c4d13bff6c..070345dbf0 100644 --- a/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp +++ b/Modules/AlgorithmsExt/src/mitkMaskImageFilter.cpp @@ -1,241 +1,129 @@ /*============================================================================ 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 "mitkMaskImageFilter.h" #include "mitkImageTimeSelector.h" #include "mitkProperties.h" #include "mitkTimeHelper.h" +#include "mitkImageTimeSelector.h" #include "mitkImageAccessByItk.h" #include "mitkImageToItk.h" -#include "itkImageRegionConstIterator.h" -#include "itkImageRegionIteratorWithIndex.h" +#include "itkMaskImageFilter.h" #include <limits> -mitk::MaskImageFilter::MaskImageFilter() : m_Mask(nullptr) +mitk::MaskImageFilter::MaskImageFilter() { this->SetNumberOfIndexedInputs(2); this->SetNumberOfRequiredInputs(2); - m_InputTimeSelector = mitk::ImageTimeSelector::New(); - m_MaskTimeSelector = mitk::ImageTimeSelector::New(); - m_OutputTimeSelector = mitk::ImageTimeSelector::New(); m_OverrideOutsideValue = false; m_OutsideValue = 0; + m_CurrentOutputTS = 0; } mitk::MaskImageFilter::~MaskImageFilter() { } void mitk::MaskImageFilter::SetMask(const mitk::Image *mask) { // Process object is not const-correct so the const_cast is required here - m_Mask = const_cast<mitk::Image *>(mask); - this->ProcessObject::SetNthInput(1, m_Mask); + auto nonconstMask = const_cast<mitk::Image *>(mask); + this->ProcessObject::SetNthInput(1, nonconstMask); } const mitk::Image *mitk::MaskImageFilter::GetMask() const { - return m_Mask; + return this->GetInput(1); +} + +mitk::Image* mitk::MaskImageFilter::GetMask() +{ + return this->GetInput(1); } void mitk::MaskImageFilter::GenerateInputRequestedRegion() { Superclass::GenerateInputRequestedRegion(); mitk::Image *output = this->GetOutput(); mitk::Image *input = this->GetInput(); - mitk::Image *mask = m_Mask; + mitk::Image *mask = this->GetMask(); if ((output->IsInitialized() == false) || (mask == nullptr) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) return; input->SetRequestedRegionToLargestPossibleRegion(); mask->SetRequestedRegionToLargestPossibleRegion(); GenerateTimeInInputRegion(output, input); - GenerateTimeInInputRegion(output, mask); } void mitk::MaskImageFilter::GenerateOutputInformation() { mitk::Image::ConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); if ((output->IsInitialized()) && (this->GetMTime() <= m_TimeOfHeaderInitialization.GetMTime())) return; itkDebugMacro(<< "GenerateOutputInformation()"); output->Initialize(input->GetPixelType(), *input->GetTimeGeometry()); output->SetPropertyList(input->GetPropertyList()->Clone()); m_TimeOfHeaderInitialization.Modified(); } -template <typename TPixel, unsigned int VImageDimension> -void mitk::MaskImageFilter::InternalComputeMask(itk::Image<TPixel, VImageDimension> *inputItkImage) +template <typename TPixel1, unsigned int VImageDimension1, typename TPixel2, unsigned int VImageDimension2> +void mitk::MaskImageFilter::InternalComputeMask(itk::Image<TPixel1, VImageDimension1>* itkInput, itk::Image<TPixel2,VImageDimension2>* itkMask) { - // dirty quick fix, duplicating code so both unsigned char and unsigned short are supported - // this should be changed once unsigned char segmentations can be converted to unsigned short - mitk::PixelType pixelType = - m_MaskTimeSelector->GetOutput()->GetImageDescriptor()->GetChannelDescriptor().GetPixelType(); - if (pixelType.GetComponentType() == itk::IOComponentEnum::UCHAR) - { - typedef itk::Image<TPixel, VImageDimension> ItkInputImageType; - typedef itk::Image<unsigned char, VImageDimension> ItkMaskImageType; - typedef itk::Image<TPixel, VImageDimension> ItkOutputImageType; - - typedef itk::ImageRegionConstIterator<ItkInputImageType> ItkInputImageIteratorType; - typedef itk::ImageRegionConstIterator<ItkMaskImageType> ItkMaskImageIteratorType; - typedef itk::ImageRegionIteratorWithIndex<ItkOutputImageType> ItkOutputImageIteratorType; - - typename mitk::ImageToItk<ItkMaskImageType>::Pointer maskimagetoitk = mitk::ImageToItk<ItkMaskImageType>::New(); - maskimagetoitk->SetInput(m_MaskTimeSelector->GetOutput()); - maskimagetoitk->Update(); - typename ItkMaskImageType::Pointer maskItkImage = maskimagetoitk->GetOutput(); - - typename mitk::ImageToItk<ItkOutputImageType>::Pointer outputimagetoitk = - mitk::ImageToItk<ItkOutputImageType>::New(); - outputimagetoitk->SetInput(m_OutputTimeSelector->GetOutput()); - outputimagetoitk->Update(); - typename ItkOutputImageType::Pointer outputItkImage = outputimagetoitk->GetOutput(); - - // create the iterators - typename ItkInputImageType::RegionType inputRegionOfInterest = inputItkImage->GetLargestPossibleRegion(); - ItkInputImageIteratorType inputIt(inputItkImage, inputRegionOfInterest); - ItkMaskImageIteratorType maskIt(maskItkImage, inputRegionOfInterest); - ItkOutputImageIteratorType outputIt(outputItkImage, inputRegionOfInterest); - - // typename ItkOutputImageType::PixelType outsideValue = itk::NumericTraits<typename - // ItkOutputImageType::PixelType>::min(); - if (!m_OverrideOutsideValue) - m_OutsideValue = itk::NumericTraits<typename ItkOutputImageType::PixelType>::min(); - - m_MinValue = std::numeric_limits<mitk::ScalarType>::max(); - m_MaxValue = std::numeric_limits<mitk::ScalarType>::min(); - - for (inputIt.GoToBegin(), maskIt.GoToBegin(), outputIt.GoToBegin(); !inputIt.IsAtEnd() && !maskIt.IsAtEnd(); - ++inputIt, ++maskIt, ++outputIt) - { - if (maskIt.Get() > itk::NumericTraits<typename ItkMaskImageType::PixelType>::Zero) - { - outputIt.Set(inputIt.Get()); - m_MinValue = std::min((float)inputIt.Get(), (float)m_MinValue); - m_MaxValue = std::max((float)inputIt.Get(), (float)m_MaxValue); - } - else - { - outputIt.Set(m_OutsideValue); - } - } - } - else - { - { - typedef itk::Image<TPixel, VImageDimension> ItkInputImageType; - typedef itk::Image<unsigned short, VImageDimension> ItkMaskImageType; - typedef itk::Image<TPixel, VImageDimension> ItkOutputImageType; - - typedef itk::ImageRegionConstIterator<ItkInputImageType> ItkInputImageIteratorType; - typedef itk::ImageRegionConstIterator<ItkMaskImageType> ItkMaskImageIteratorType; - typedef itk::ImageRegionIteratorWithIndex<ItkOutputImageType> ItkOutputImageIteratorType; - - typename mitk::ImageToItk<ItkMaskImageType>::Pointer maskimagetoitk = mitk::ImageToItk<ItkMaskImageType>::New(); - maskimagetoitk->SetInput(m_MaskTimeSelector->GetOutput()); - maskimagetoitk->Update(); - typename ItkMaskImageType::Pointer maskItkImage = maskimagetoitk->GetOutput(); - - typename mitk::ImageToItk<ItkOutputImageType>::Pointer outputimagetoitk = - mitk::ImageToItk<ItkOutputImageType>::New(); - outputimagetoitk->SetInput(m_OutputTimeSelector->GetOutput()); - outputimagetoitk->Update(); - typename ItkOutputImageType::Pointer outputItkImage = outputimagetoitk->GetOutput(); - - // create the iterators - typename ItkInputImageType::RegionType inputRegionOfInterest = inputItkImage->GetLargestPossibleRegion(); - ItkInputImageIteratorType inputIt(inputItkImage, inputRegionOfInterest); - ItkMaskImageIteratorType maskIt(maskItkImage, inputRegionOfInterest); - ItkOutputImageIteratorType outputIt(outputItkImage, inputRegionOfInterest); - - // typename ItkOutputImageType::PixelType outsideValue = itk::NumericTraits<typename - // ItkOutputImageType::PixelType>::min(); - if (!m_OverrideOutsideValue) - m_OutsideValue = itk::NumericTraits<typename ItkOutputImageType::PixelType>::min(); - - m_MinValue = std::numeric_limits<mitk::ScalarType>::max(); - m_MaxValue = std::numeric_limits<mitk::ScalarType>::min(); - - for (inputIt.GoToBegin(), maskIt.GoToBegin(), outputIt.GoToBegin(); !inputIt.IsAtEnd() && !maskIt.IsAtEnd(); - ++inputIt, ++maskIt, ++outputIt) - { - if (maskIt.Get() > itk::NumericTraits<typename ItkMaskImageType::PixelType>::Zero) - { - outputIt.Set(inputIt.Get()); - m_MinValue = std::min((float)inputIt.Get(), (float)m_MinValue); - m_MaxValue = std::max((float)inputIt.Get(), (float)m_MaxValue); - } - else - { - outputIt.Set(m_OutsideValue); - } - } - } - } + using MaskFilterType = itk::MaskImageFilter< itk::Image<TPixel1, VImageDimension1>, itk::Image<TPixel2, VImageDimension2>>; + + auto maskFilter = MaskFilterType::New(); + maskFilter->SetInput(itkInput); + maskFilter->SetMaskImage(itkMask); + maskFilter->SetOutsideValue(m_OutsideValue); + maskFilter->Update(); + + auto output = maskFilter->GetOutput(); + + // re-import to MITK + auto mitkOutput = this->GetOutput(); + mitkOutput->SetVolume(output->GetBufferPointer(), m_CurrentOutputTS); } void mitk::MaskImageFilter::GenerateData() { - mitk::Image::ConstPointer input = this->GetInput(); - mitk::Image::Pointer mask = m_Mask; + auto input = this->GetInput(); + auto mask = this->GetMask(); mitk::Image::Pointer output = this->GetOutput(); - if ((output->IsInitialized() == false) || (mask.IsNull()) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) + if ((output->IsInitialized() == false) || (nullptr == mask) || (mask->GetTimeGeometry()->CountTimeSteps() == 0)) return; - m_InputTimeSelector->SetInput(input); - m_MaskTimeSelector->SetInput(mask); - m_OutputTimeSelector->SetInput(this->GetOutput()); - - mitk::Image::RegionType outputRegion = output->GetRequestedRegion(); - const mitk::TimeGeometry *outputTimeGeometry = output->GetTimeGeometry(); const mitk::TimeGeometry *inputTimeGeometry = input->GetTimeGeometry(); - const mitk::TimeGeometry *maskTimeGeometry = mask->GetTimeGeometry(); - ScalarType timeInMS; - int timestep = 0; - int tstart = outputRegion.GetIndex(3); - int tmax = tstart + outputRegion.GetSize(3); - - int t; - for (t = tstart; t < tmax; ++t) + for (TimeStepType t = 0; t < inputTimeGeometry->CountTimeSteps(); ++t) { - timeInMS = outputTimeGeometry->TimeStepToTimePoint(t); - - timestep = inputTimeGeometry->TimePointToTimeStep(timeInMS); - - m_InputTimeSelector->SetTimeNr(timestep); - m_InputTimeSelector->UpdateLargestPossibleRegion(); - m_OutputTimeSelector->SetTimeNr(t); - m_OutputTimeSelector->UpdateLargestPossibleRegion(); - - timestep = maskTimeGeometry->TimePointToTimeStep(timeInMS); - m_MaskTimeSelector->SetTimeNr(timestep); - m_MaskTimeSelector->UpdateLargestPossibleRegion(); + auto timeInMS = inputTimeGeometry->TimeStepToTimePoint(t); - AccessByItk(m_InputTimeSelector->GetOutput(), InternalComputeMask); + m_CurrentOutputTS = t; + auto inputFrame = SelectImageByTimePoint(input, timeInMS); + auto maskFrame = SelectImageByTimePoint(mask, timeInMS); + AccessTwoImagesFixedDimensionByItk(inputFrame, maskFrame, InternalComputeMask, 3); } m_TimeOfHeaderInitialization.Modified(); } diff --git a/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h index 44130cb7e6..72c61fbad6 100644 --- a/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSingleNodeSelectionWidget.h @@ -1,97 +1,98 @@ /*============================================================================ 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 QmitkSingleNodeSelectionWidget_h #define QmitkSingleNodeSelectionWidget_h #include <MitkQtWidgetsExports.h> #include <ui_QmitkSingleNodeSelectionWidget.h> #include <mitkDataStorage.h> #include <mitkWeakPointer.h> #include <mitkNodePredicateBase.h> #include <QmitkAbstractNodeSelectionWidget.h> #include <QmitkNodeSelectionButton.h> class QmitkAbstractDataStorageModel; /** * @class QmitkSingleNodeSelectionWidget * @brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it * allows to change the selection. * * @remark This class provides a public function 'SetAutoSelectNewNodes' that can be used to enable * the auto selection mode (default is false). * The user of this class calling this function has to make sure that the base-class Q_SIGNAL * 'CurrentSelectionChanged', which will be emitted by this function, is already * connected to a receiving slot, if the initial valid auto selection should not get lost. */ class MITKQTWIDGETS_EXPORT QmitkSingleNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkSingleNodeSelectionWidget(QWidget* parent = nullptr); mitk::DataNode::Pointer GetSelectedNode() const; bool GetAutoSelectNewNodes() const; using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; public Q_SLOTS: void SetCurrentSelectedNode(mitk::DataNode* selectedNode); /** * Sets the auto selection mode (default is false). * If auto select is true and the following conditions are fulfilled, the widget will * select a node automatically from the data storage: * - a data storage is set * - data storage contains at least one node that matches the given predicate * - no selection is set * * @remark Enabling the auto selection mode by calling 'SetAutoSelectNewNodes(true)' * will directly emit a 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' Q_SIGNAL * if a valid auto selection was made. * If this initial emission should not get lost, auto selection mode needs to be enabled after this * selection widget has been connected via the 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' * Q_SIGNAL to a receiving function. */ void SetAutoSelectNewNodes(bool autoSelect); protected Q_SLOTS: virtual void OnClearSelection(); protected: void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; bool eventFilter(QObject *obj, QEvent *ev) override; void EditSelection(); void UpdateInfo() override; void OnDataStorageChanged() override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; + void OnNodePredicateChanged() override; void AutoSelectNodes(); /** Helper function that gets a suitable auto selected node from the datastorage that fits to the predicate settings. @param ignoreNodes You may pass a list of nodes that must not be chosen as auto selected node. */ mitk::DataNode::Pointer DetermineAutoSelectNode(const NodeList& ignoreNodes = {}); /** See documentation of SetAutoSelectNewNodes for details*/ bool m_AutoSelectNodes; Ui_QmitkSingleNodeSelectionWidget m_Controls; }; #endif diff --git a/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp index fc30ea2679..d4c462c133 100644 --- a/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSingleNodeSelectionWidget.cpp @@ -1,240 +1,245 @@ /*============================================================================ 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 "QmitkSingleNodeSelectionWidget.h" #include <mitkNodePredicateFunction.h> #include <mitkNodePredicateAnd.h> #include <QmitkNodeSelectionDialog.h> #include <QmitkNodeDetailsDialog.h> #include <QmitkStyleManager.h> #include <QMouseEvent> QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) , m_AutoSelectNodes(false) { m_Controls.setupUi(this); m_Controls.btnSelect->installEventFilter(this); m_Controls.btnSelect->setVisible(true); m_Controls.btnClear->setVisible(false); m_Controls.btnClear->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/times.svg"))); this->UpdateInfo(); connect(m_Controls.btnClear, SIGNAL(clicked(bool)), this, SLOT(OnClearSelection())); } void QmitkSingleNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { if (newInternalSelection.empty()) { if (m_AutoSelectNodes) { auto autoSelectedNode = this->DetermineAutoSelectNode(oldInternalSelection); if (autoSelectedNode.IsNotNull()) { newInternalSelection.append(autoSelectedNode); } } } else if (newInternalSelection.size()>1) { //this widget only allows one internal selected node. newInternalSelection = { newInternalSelection.front() }; } } void QmitkSingleNodeSelectionWidget::OnClearSelection() { if (m_IsOptional) { this->SetCurrentSelection({}); } this->UpdateInfo(); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::GetSelectedNode() const { mitk::DataNode::Pointer result; auto selection = GetCurrentInternalSelection(); if (!selection.empty()) { result = selection.front(); } return result; } bool QmitkSingleNodeSelectionWidget::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_Controls.btnSelect) { if (ev->type() == QEvent::MouseButtonRelease) { auto mouseEv = dynamic_cast<QMouseEvent*>(ev); if (!mouseEv) { return false; } if (mouseEv->button() == Qt::LeftButton) { if (this->isEnabled()) { this->EditSelection(); return true; } } else { auto selection = this->CompileEmitSelection(); if (!selection.empty()) { QmitkNodeDetailsDialog infoDialog(selection, this); infoDialog.exec(); return true; } } } } return false; } void QmitkSingleNodeSelectionWidget::EditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->GetCurrentInternalSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); m_Controls.btnSelect->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnSelect->setChecked(false); delete dialog; } void QmitkSingleNodeSelectionWidget::UpdateInfo() { if (this->GetSelectedNode().IsNull()) { if (m_IsOptional) { m_Controls.btnSelect->SetNodeInfo(m_EmptyInfo); } else { m_Controls.btnSelect->SetNodeInfo(m_InvalidInfo); } m_Controls.btnSelect->SetSelectionIsOptional(m_IsOptional); m_Controls.btnClear->setVisible(false); } else { m_Controls.btnClear->setVisible(m_IsOptional); } m_Controls.btnSelect->SetSelectedNode(this->GetSelectedNode()); } void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) { NodeList selection; if (selectedNode) { selection.append(selectedNode); } this->SetCurrentSelection(selection); } void QmitkSingleNodeSelectionWidget::OnDataStorageChanged() { this->AutoSelectNodes(); } void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) { this->AutoSelectNodes(); } +void QmitkSingleNodeSelectionWidget::OnNodePredicateChanged() +{ + this->AutoSelectNodes(); +} + bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const { return m_AutoSelectNodes; } void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) { m_AutoSelectNodes = autoSelect; this->AutoSelectNodes(); } void QmitkSingleNodeSelectionWidget::AutoSelectNodes() { if (this->GetSelectedNode().IsNull() && m_AutoSelectNodes) { auto autoNode = this->DetermineAutoSelectNode(); if (autoNode.IsNotNull()) { this->HandleChangeOfInternalSelection({ autoNode }); } } } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::DetermineAutoSelectNode(const NodeList& ignoreNodes) { mitk::DataNode::Pointer result; auto storage = m_DataStorage.Lock(); if (storage.IsNotNull()) { auto ignoreCheck = [ignoreNodes](const mitk::DataNode * node) { bool result = true; for (const auto& ignoreNode : ignoreNodes) { if (node == ignoreNode) { result = false; break; } } return result; }; mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); if (m_NodePredicate.IsNotNull()) { predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); } result = storage->GetNode(predicate); } return result; } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp index f4975aac24..0ae005d4d2 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkConvertToMultiLabelSegmentationWidget.cpp @@ -1,543 +1,546 @@ /*============================================================================ 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 "QmitkConvertToMultiLabelSegmentationWidget.h" #include <ui_QmitkConvertToMultiLabelSegmentationWidgetControls.h> #include <mitkDataStorage.h> #include <mitkException.h> #include <mitkExceptionMacro.h> #include <mitkProgressBar.h> #include <mitkProperties.h> #include <mitkSurfaceToImageFilter.h> #include <mitkSurface.h> #include <mitkContourModel.h> #include <mitkContourModelSet.h> #include <mitkContourModelSetToImageFilter.h> #include <mitkLabelSetImage.h> #include <mitkMultiLabelPredicateHelper.h> #include <mitkNodePredicateOr.h> #include <mitkNodePredicateNot.h> #include <mitkNodePredicateAnd.h> #include <mitkNodePredicateDataType.h> #include <mitkNodePredicateSubGeometry.h> #include <mitkNodePredicateGeometry.h> #include <mitkNodePredicateProperty.h> #include <mitkLabelSetImageHelper.h> #include <mitkLabelSetImageConverter.h> #include <QmitkNodeSelectionDialog.h> -mitk::NodePredicateBase::Pointer GetInputPredicate() +namespace { - auto isImage = mitk::TNodePredicateDataType<mitk::Image>::New(); - auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); + mitk::NodePredicateBase::Pointer GetInputPredicate() + { + auto isImage = mitk::TNodePredicateDataType<mitk::Image>::New(); + auto isNotSeg = mitk::NodePredicateNot::New(mitk::GetMultiLabelSegmentationPredicate()); - auto isSurface = mitk::TNodePredicateDataType<mitk::Surface>::New(); - auto isContourModel = mitk::TNodePredicateDataType<mitk::ContourModel>::New(); - auto isContourModelSet = mitk::TNodePredicateDataType<mitk::ContourModelSet>::New(); + auto isSurface = mitk::TNodePredicateDataType<mitk::Surface>::New(); + auto isContourModel = mitk::TNodePredicateDataType<mitk::ContourModel>::New(); + auto isContourModelSet = mitk::TNodePredicateDataType<mitk::ContourModelSet>::New(); - auto isData = mitk::NodePredicateOr::New(isImage, isContourModel, isContourModelSet); - isData->AddPredicate(isSurface); + auto isData = mitk::NodePredicateOr::New(isImage, isContourModel, isContourModelSet); + isData->AddPredicate(isSurface); - auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isData); - isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); - isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); - return isValidInput.GetPointer(); + auto isValidInput = mitk::NodePredicateAnd::New(isNotSeg, isData); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); + isValidInput->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); + return isValidInput.GetPointer(); + } } const mitk::DataNode* GetNodeWithLargestImageGeometry(const QmitkNodeSelectionDialog::NodeList& nodes) { mitk::BaseGeometry::ConstPointer refGeometry; mitk::DataNode* result = nullptr; for (auto& node : nodes) { auto castedData = dynamic_cast<const mitk::Image*>(node->GetData()); if (castedData != nullptr) { if (refGeometry.IsNull() || mitk::IsSubGeometry(*refGeometry, *(castedData->GetGeometry()), mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, mitk::NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION)) { refGeometry = castedData->GetGeometry(); result = node; } } } return result; } QmitkNodeSelectionDialog::NodeList GetNonimageNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QmitkNodeSelectionDialog::NodeList result; for (auto& node : nodes) { auto castedData = dynamic_cast<const mitk::Image*>(node->GetData()); if (castedData == nullptr) { result.push_back(node); } } return result; } QmitkNodeSelectionDialog::NodeList GetImageNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QmitkNodeSelectionDialog::NodeList result; for (auto& node : nodes) { auto castedData = dynamic_cast<const mitk::Image*>(node->GetData()); if (castedData != nullptr) { result.push_back(node); } } return result; } QmitkNodeSelectionDialog::SelectionCheckFunctionType CheckForSameGeometry(const mitk::DataNode* refNode) { mitk::DataNode::ConstPointer refNodeLambda = refNode; auto lambda = [refNodeLambda](const QmitkNodeSelectionDialog::NodeList& nodes) { if (nodes.empty()) { return std::string(); } mitk::NodePredicateSubGeometry::Pointer geoPredicate; bool usedExternalGeo = false; std::string refNodeName; if (refNodeLambda.IsNotNull() && nullptr != refNodeLambda->GetData()) { geoPredicate = mitk::NodePredicateSubGeometry::New(refNodeLambda->GetData()->GetGeometry()); usedExternalGeo = true; refNodeName = refNodeLambda->GetName(); } if (geoPredicate.IsNull()) { auto imageNode = GetNodeWithLargestImageGeometry(nodes); if (nullptr != imageNode) { geoPredicate = mitk::NodePredicateSubGeometry::New(imageNode->GetData()->GetGeometry()); refNodeName = imageNode->GetName(); } } for (auto& node : nodes) { auto castedImageData = dynamic_cast<const mitk::Image*>(node->GetData()); if (nullptr != castedImageData) { if (!geoPredicate->CheckNode(node)) { std::stringstream ss; ss << "<font class=\"warning\"><p>Invalid selection: All selected images must have the same geometry or a sub geometry "; if (usedExternalGeo) ss << "of the selected reference/output"; ss << ".< / p><p>Uses reference data: \""; ss << refNodeName << "\"</p>"; ss << "<p>Differing data selections i.a.: \""; ss << node->GetName() << "\"</p></font>"; return ss.str(); } } } return std::string(); }; return lambda; } QmitkConvertToMultiLabelSegmentationWidget::QmitkConvertToMultiLabelSegmentationWidget(mitk::DataStorage* dataStorage, QWidget* parent) : QWidget(parent), m_DataStorage(dataStorage) { m_Controls = new Ui::QmitkConvertToMultiLabelSegmentationWidgetControls; m_Controls->setupUi(this); m_Controls->inputNodesSelector->SetDataStorage(dataStorage); m_Controls->inputNodesSelector->SetNodePredicate(GetInputPredicate()); m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(nullptr)); m_Controls->inputNodesSelector->SetSelectionIsOptional(false); m_Controls->inputNodesSelector->SetInvalidInfo(QStringLiteral("Please select inputs (images, surfaces or contours) for conversion")); m_Controls->inputNodesSelector->SetPopUpTitel(QStringLiteral("Select inputs")); m_Controls->inputNodesSelector->SetPopUpHint(QStringLiteral("You may select multiple inputs for conversion. But all selected images must have the same geometry or a sub geometry.")); m_Controls->outputSegSelector->SetDataStorage(dataStorage); m_Controls->outputSegSelector->SetNodePredicate(mitk::GetMultiLabelSegmentationPredicate()); m_Controls->outputSegSelector->SetSelectionIsOptional(false); m_Controls->outputSegSelector->SetInvalidInfo(QStringLiteral("Please select the target segmentation")); m_Controls->outputSegSelector->SetPopUpTitel(QStringLiteral("Select target segmentation")); m_Controls->outputSegSelector->SetPopUpHint(QStringLiteral("Select the segmentation to which the converted inputs should be added.")); m_Controls->outputSegSelector->SetAutoSelectNewNodes(true); m_Controls->refNodeSelector->SetDataStorage(dataStorage); m_Controls->refNodeSelector->SetNodePredicate(mitk::NodePredicateOr::New(GetInputPredicate(),mitk::GetMultiLabelSegmentationPredicate())); m_Controls->refNodeSelector->SetSelectionIsOptional(false); m_Controls->refNodeSelector->SetInvalidInfo(QStringLiteral("Please select a reference image or segmentation")); m_Controls->refNodeSelector->SetPopUpTitel(QStringLiteral("Select a reference image or segmentation")); m_Controls->refNodeSelector->SetPopUpHint(QStringLiteral("Select the image or segmentation that defines the geometry of the conversion result.")); this->ConfigureWidgets(); connect (m_Controls->btnConvert, &QAbstractButton::clicked, this, &QmitkConvertToMultiLabelSegmentationWidget::OnConvertPressed); connect(m_Controls->inputNodesSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnInputSelectionChanged); connect(m_Controls->refNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnRefSelectionChanged); connect(m_Controls->outputSegSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkConvertToMultiLabelSegmentationWidget::OnOutputSelectionChanged); auto widget = this; connect(m_Controls->radioAddToSeg, &QRadioButton::toggled, m_Controls->outputSegSelector, [widget](bool) {widget->ConfigureWidgets(); }); connect(m_Controls->checkMultipleOutputs, &QCheckBox::toggled, m_Controls->outputSegSelector, [widget](bool) {widget->ConfigureWidgets(); }); } QmitkConvertToMultiLabelSegmentationWidget::~QmitkConvertToMultiLabelSegmentationWidget() { } void QmitkConvertToMultiLabelSegmentationWidget::ConfigureWidgets() { m_InternalEvent = true; if (m_Controls->radioAddToSeg->isChecked()) { auto selectedNode = m_Controls->outputSegSelector->GetSelectedNode(); m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(selectedNode)); } else { m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(nullptr)); } m_Controls->outputSegSelector->setVisible(m_Controls->radioAddToSeg->isChecked()); if (m_Controls->radioNewSeg->isChecked()) { auto selectedNode = m_Controls->refNodeSelector->GetSelectedNode(); } m_Controls->checkMultipleOutputs->setVisible(m_Controls->radioNewSeg->isChecked()); bool refNeeded = m_Controls->radioNewSeg->isChecked() && !m_Controls->inputNodesSelector->GetSelectedNodes().empty() && nullptr == GetNodeWithLargestImageGeometry(m_Controls->inputNodesSelector->GetSelectedNodes()); m_Controls->refNodeSelector->setVisible(refNeeded); if (refNeeded) m_Controls->inputNodesSelector->SetSelectionCheckFunction(CheckForSameGeometry(m_Controls->refNodeSelector->GetSelectedNode())); m_Controls->groupGrouping->setVisible(m_Controls->radioAddToSeg->isChecked() || !(m_Controls->checkMultipleOutputs->isChecked())); bool inputIsOK = !m_Controls->inputNodesSelector->GetSelectedNodes().empty() && !m_Controls->inputNodesSelector->CurrentSelectionViolatesCheckFunction(); bool outputIsOK = !m_Controls->radioAddToSeg->isChecked() || m_Controls->outputSegSelector->GetSelectedNode().IsNotNull(); bool refIsOK = !m_Controls->radioNewSeg->isChecked() || !m_Controls->refNodeSelector->isVisible() || m_Controls->refNodeSelector->GetSelectedNode().IsNotNull(); m_Controls->btnConvert->setEnabled(inputIsOK && outputIsOK && refIsOK); m_InternalEvent = false; } void QmitkConvertToMultiLabelSegmentationWidget::OnInputSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnOutputSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnRefSelectionChanged(QmitkAbstractNodeSelectionWidget::NodeList /*nodes*/) { if (!m_InternalEvent) this->ConfigureWidgets(); } void QmitkConvertToMultiLabelSegmentationWidget::OnConvertPressed() { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { mitkThrow() << "QmitkConvertToMultiLabelSegmentationWidget is in invalid state. No datastorage is set."; } auto nodes = m_Controls->inputNodesSelector->GetSelectedNodes(); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(3 * nodes.size() + 1); if (m_Controls->radioNewSeg->isChecked() && m_Controls->checkMultipleOutputs->isChecked()) { for (auto& node : nodes) { this->ConvertNodes({ node }); } } else { this->ConvertNodes(nodes); } } mitk::Image::Pointer ConvertSurfaceToImage(const mitk::Image* refImage, const mitk::Surface* surface) { mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->UShortBinaryPixelTypeOn(); surfaceToImageFilter->SetInput(surface); surfaceToImageFilter->SetImage(refImage); try { surfaceToImageFilter->Update(); } catch (itk::ExceptionObject& excpt) { MITK_ERROR << excpt.GetDescription(); return nullptr; } return surfaceToImageFilter->GetOutput(); } mitk::Image::Pointer ConvertContourModelSetToImage(mitk::Image* refImage, mitk::ContourModelSet* contourSet) { // Use mitk::ContourModelSetToImageFilter to fill the ContourModelSet into the image mitk::ContourModelSetToImageFilter::Pointer contourFiller = mitk::ContourModelSetToImageFilter::New(); contourFiller->SetImage(refImage); contourFiller->SetInput(contourSet); contourFiller->MakeOutputLabelPixelTypeOn(); try { contourFiller->Update(); } catch (const std::exception& e) { MITK_ERROR << "Error while converting contour model. " << e.what(); } catch (...) { MITK_ERROR << "Unknown error while converting contour model."; } return contourFiller->GetOutput(); } void CheckForLabelCollision(const QmitkNodeSelectionDialog::NodeList& nodes, const std::map<const mitk::DataNode*, mitk::LabelSetImage::LabelValueVectorType>& foundLabelsMap, mitk::LabelSetImage::LabelValueVectorType& usedLabelValues, std::map<const mitk::DataNode*, mitk::LabelValueMappingVector>& labelsMappingMap) { for (const auto& node : nodes) { mitk::ProgressBar::GetInstance()->Progress(); const auto& foundLabels = foundLabelsMap.at(node); mitk::LabelSetImage::LabelValueVectorType correctedLabelValues; mitk::CheckForLabelValueConflictsAndResolve(foundLabels, usedLabelValues, correctedLabelValues); mitk::LabelValueMappingVector mapping; std::transform(foundLabels.begin(), foundLabels.end(), correctedLabelValues.begin(), std::back_inserter(mapping), [](mitk::LabelSetImage::LabelValueType a, mitk::LabelSetImage::LabelValueType b) { return std::make_pair(a, b); }); labelsMappingMap.emplace(node, mapping); } } void QmitkConvertToMultiLabelSegmentationWidget::ConvertNodes(const QmitkNodeSelectionDialog::NodeList& nodes) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); auto nonimageNodes = GetNonimageNodes(nodes); auto imageNodes = GetImageNodes(nodes); mitk::LabelSetImage::Pointer outputSeg; mitk::Image::Pointer refImage; const mitk::DataNode* refNode; if (m_Controls->radioAddToSeg->isChecked()) { outputSeg = dynamic_cast<mitk::LabelSetImage*>(m_Controls->outputSegSelector->GetSelectedNode()->GetData()); if (outputSeg->GetNumberOfLayers() > 0) { refImage = outputSeg->GetGroupImage(0); } else { //in case we work with a output seg, we need to generate a template image //reason is that the conversion filters used for surfaces or contours need images //as reference and MultiLabelSegmentations is currently empty. refImage = mitk::Image::New(); refImage->Initialize(mitk::MakePixelType<mitk::LabelSetImage::LabelValueType, mitk::LabelSetImage::LabelValueType, 1>(), *(outputSeg->GetTimeGeometry())); } } else { outputSeg = mitk::LabelSetImage::New(); auto inputNode = GetNodeWithLargestImageGeometry(m_Controls->inputNodesSelector->GetSelectedNodes()); if (nullptr != inputNode) { refNode = inputNode; refImage = dynamic_cast<mitk::Image*>(inputNode->GetData()); outputSeg->Initialize(refImage); } else { refNode = m_Controls->refNodeSelector->GetSelectedNode(); refImage = dynamic_cast<mitk::Image*>(refNode->GetData()); outputSeg->Initialize(refImage); } } //convert non-image nodes to images std::map<const mitk::DataNode*, mitk::Image::Pointer> preparedImageMap; std::map<const mitk::DataNode*, mitk::LabelSetImage::LabelValueVectorType> foundLabelsMap; for (const auto& node : nonimageNodes) { mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer convertedImage; auto surface = dynamic_cast<mitk::Surface*>(node->GetData()); auto contourModel = dynamic_cast<mitk::ContourModel*>(node->GetData()); auto contourModelSet = dynamic_cast<mitk::ContourModelSet*>(node->GetData()); if (nullptr != surface) { convertedImage = ConvertSurfaceToImage(refImage, surface); } else if (nullptr != contourModelSet) { convertedImage = ConvertContourModelSetToImage(refImage, contourModelSet); } else if (nullptr != contourModel) { auto contourModelSet = mitk::ContourModelSet::New(); contourModelSet->AddContourModel(contourModel); convertedImage = ConvertContourModelSetToImage(refImage, contourModelSet); } else { mitkThrow() << "Invalid state of QmitkConvertToMultiLabelSegmentationWidget. At least one input is of invalid type, that should not be possible to select. Invalid node: " << *(node.GetPointer()); } if (convertedImage.IsNotNull()) { preparedImageMap.emplace(node, convertedImage); //all non-image data is converted to binary maps foundLabelsMap.emplace(node, mitk::LabelSetImage::LabelValueVectorType({ 1 })); } else { mitkThrow() << "Failed to convert an input. Failed node: " << *(node.GetPointer()); } } //prepare image nodes and get contained labels for (const auto& node : imageNodes) { mitk::ProgressBar::GetInstance()->Progress(); mitk::LabelSetImage::LabelValueVectorType foundLabels; mitk::ProgressBar::GetInstance()->Progress(); mitk::Image::Pointer convertedImage = mitk::ConvertImageToGroupImage(dynamic_cast<mitk::Image*>(node->GetData()), foundLabels); preparedImageMap.emplace(node, convertedImage); foundLabelsMap.emplace(node, foundLabels); } //check for label collision and fix if needed mitk::LabelSetImage::LabelValueVectorType usedLabelValues = outputSeg->GetAllLabelValues(); std::map<const mitk::DataNode*, mitk::LabelValueMappingVector> labelsMappingMap; CheckForLabelCollision(imageNodes, foundLabelsMap, usedLabelValues, labelsMappingMap); CheckForLabelCollision(nonimageNodes, foundLabelsMap, usedLabelValues, labelsMappingMap); //Ensure that we have the first layer to add mitk::LabelSetImage::GroupIndexType currentGroupIndex = 0; if (m_Controls->radioAddToSeg->isChecked() || 0 == outputSeg->GetNumberOfLayers()) { currentGroupIndex = outputSeg->AddLayer(); } //Transfer content and add labels for (const auto& node : imageNodes) { mitk::ProgressBar::GetInstance()->Progress(); if (m_Controls->radioSingleGroup->isChecked() && node != imageNodes.front()) currentGroupIndex = outputSeg->AddLayer(); const auto& labelsMapping = labelsMappingMap.at(node); for (auto [oldV, correctedV] : labelsMapping) { std::string name = "Value " + std::to_string(oldV); if (m_Controls->radioMergeGroup->isChecked()) name = node->GetName() + " " + name; auto label = mitk::LabelSetImageHelper::CreateNewLabel(outputSeg, name, true); label->SetValue(correctedV); outputSeg->AddLabel(label, currentGroupIndex, false, false); } mitk::TransferLabelContent(preparedImageMap.at(node), outputSeg->GetGroupImage(currentGroupIndex), outputSeg->GetConstLabelsByValue(outputSeg->GetLabelValuesByGroup(currentGroupIndex)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, labelsMapping); } for (const auto& node : nonimageNodes) { mitk::ProgressBar::GetInstance()->Progress(); if (m_Controls->radioSingleGroup->isChecked() && (node != nonimageNodes.front() || !imageNodes.empty())) currentGroupIndex = outputSeg->AddLayer(); const auto& labelsMapping = labelsMappingMap.at(node); for (auto [oldV, correctedV] : labelsMapping) { auto label = mitk::LabelSetImageHelper::CreateNewLabel(outputSeg, node->GetName(), true); label->SetValue(correctedV); mitk::ColorProperty::ConstPointer colorProp = dynamic_cast<const mitk::ColorProperty*>(node->GetConstProperty("color").GetPointer()); if (colorProp.IsNotNull()) label->SetColor(colorProp->GetColor()); outputSeg->AddLabel(label, currentGroupIndex, false, false); } mitk::TransferLabelContent(preparedImageMap.at(node), outputSeg->GetGroupImage(currentGroupIndex), outputSeg->GetConstLabelsByValue(outputSeg->GetLabelValuesByGroup(currentGroupIndex)), mitk::LabelSetImage::UNLABELED_VALUE, mitk::LabelSetImage::UNLABELED_VALUE, false, labelsMapping); } if (m_Controls->radioAddToSeg->isChecked()) { m_Controls->outputSegSelector->GetSelectedNode()->Modified(); } else { mitk::DataNode::Pointer outNode = mitk::DataNode::New(); std::stringstream stream; stream << "ConvertedSeg"; if (nodes.size() == 1) { stream << "_" << nodes.front()->GetName(); } outNode->SetName(stream.str()); outNode->SetData(outputSeg); auto dataStorage = m_DataStorage.Lock(); dataStorage->Add(outNode); } mitk::ProgressBar::GetInstance()->Reset(); QApplication::restoreOverrideCursor(); } diff --git a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp index 884c5a79cd..188eedead7 100644 --- a/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp +++ b/Modules/SegmentationUI/SegmentationUtilities/QmitkImageMaskingWidget.cpp @@ -1,389 +1,286 @@ /*============================================================================ 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 <ui_QmitkImageMaskingWidgetControls.h> #include <mitkException.h> #include <mitkExceptionMacro.h> #include <mitkImage.h> #include <mitkImageStatisticsHolder.h> #include <mitkMaskImageFilter.h> #include <mitkProgressBar.h> -#include <mitkSurfaceToImageFilter.h> #include <mitkImageAccessByItk.h> #include <mitkNodePredicateAnd.h> -#include <mitkNodePredicateDimension.h> #include <mitkNodePredicateGeometry.h> #include <mitkNodePredicateNot.h> +#include <mitkNodePredicateDataType.h> +#include <mitkNodePredicateProperty.h> +#include <mitkMultiLabelPredicateHelper.h> +#include <mitkLabelSetImageConverter.h> #include <QMessageBox> #include <limits> namespace { - bool IsSurface(const mitk::DataNode* dataNode) + mitk::NodePredicateBase::Pointer GetInputPredicate() { - if (nullptr != dataNode) - { - if (nullptr != dynamic_cast<const mitk::Surface*>(dataNode->GetData())) - return true; - } + auto isImage = mitk::TNodePredicateDataType<mitk::Image>::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 an 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 a 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.\nThe segmentation must have the same geometry as 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<mitk::Image*> ( dataSelectionWidget->GetSelection(0)->GetData() ); - mitk::Image::Pointer maskImage = dynamic_cast<mitk::Image*> ( dataSelectionWidget->GetSelection(1)->GetData() ); +void QmitkImageMaskingWidget::ConfigureWidgets() +{ + auto iNode = m_Controls->imageNodeSelector->GetSelectedNode(); + auto sNode = m_Controls->segNodeSelector->GetSelectedNode(); - if (maskImage.IsNull()) - { - dataSelectionWidget->SetHelpText("Different image sizes cannot be masked"); - this->EnableButtons(false); - return; - } - } + bool enable = iNode.IsNotNull() && sNode.IsNotNull() && !m_Controls->labelInspector->GetSelectedLabels().empty(); - else - { - dataSelectionWidget->SetHelpText(HelpText); - return; - } - } - - dataSelectionWidget->SetHelpText(""); - this->EnableButtons(); + this->EnableButtons(enable); } 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; - } + mitk::LabelSetImage::Pointer segmentation = m_Controls->labelInspector->GetMultiLabelSegmentation(); + mitk::Image::Pointer referenceImage = static_cast<mitk::Image*>(m_Controls->imageNodeSelector->GetSelectedNode()->GetData()); - //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; - } - - 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::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( "<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(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 <MitkSegmentationUIExports.h> #include <mitkDataStorage.h> -#include <mitkSurface.h> +#include <QmitkAbstractNodeSelectionWidget.h> #include <QWidget> 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<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); + mitk::WeakPointer<mitk::DataStorage> 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 @@ <?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>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> + <widget class="QmitkSingleNodeSelectionWidget" name="imageNodeSelector" native="true"/> + </item> + <item> + <widget class="QmitkSingleNodeSelectionWidget" name="segNodeSelector" native="true"/> + </item> + <item> + <widget class="QmitkMultiLabelInspector" name="labelInspector" native="true"/> </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> + <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/> <connections/> </ui>