diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp deleted file mode 100644 index 903be794ef..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/*============================================================================ - -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. - -============================================================================*/ - -// MITK -#include "mitkAutoMLSegmentationWithPreviewTool.h" -#include "mitkImageAccessByItk.h" -#include "mitkToolManager.h" -#include -#include -#include -#include -#include -#include -#include - -// ITK -#include -#include - -mitk::AutoMLSegmentationWithPreviewTool::AutoMLSegmentationWithPreviewTool() : AutoSegmentationWithPreviewTool(true) -{ -} - -void mitk::AutoMLSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& regions) -{ - if (m_SelectedLabels != regions) - { - m_SelectedLabels = regions; - //Note: we do not call this->Modified() on puprose. Reason: changing the - //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. - } -} - -const mitk::LabelSetImage* mitk::AutoMLSegmentationWithPreviewTool::GetMLPreview() const -{ - if (m_MLPreviewNode.IsNotNull()) - { - const auto mlPreviewImage = dynamic_cast(this->m_MLPreviewNode->GetData()); - return mlPreviewImage; - } - - return nullptr; -} - -mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType mitk::AutoMLSegmentationWithPreviewTool::GetSelectedLabels() const -{ - return this->m_SelectedLabels; -} - -void mitk::AutoMLSegmentationWithPreviewTool::Activated() -{ - Superclass::Activated(); - - m_SelectedLabels = {}; - - m_MLPreviewNode = mitk::DataNode::New(); - m_MLPreviewNode->SetProperty("name", StringProperty::New(std::string(this->GetName()) + "ML preview")); - m_MLPreviewNode->SetProperty("helper object", BoolProperty::New(true)); - m_MLPreviewNode->SetVisibility(true); - m_MLPreviewNode->SetOpacity(1.0); - - this->GetToolManager()->GetDataStorage()->Add(m_MLPreviewNode); -} - -void mitk::AutoMLSegmentationWithPreviewTool::Deactivated() -{ - this->GetToolManager()->GetDataStorage()->Remove(m_MLPreviewNode); - m_MLPreviewNode = nullptr; - - Superclass::Deactivated(); -} - -void mitk::AutoMLSegmentationWithPreviewTool::UpdateCleanUp() -{ - if (m_MLPreviewNode.IsNotNull()) - m_MLPreviewNode->SetVisibility(m_SelectedLabels.empty()); - - if (nullptr != this->GetPreviewSegmentationNode()) - this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedLabels.empty()); - - if (m_SelectedLabels.empty()) - { - this->ResetPreviewContent(); - } -} - -void mitk::AutoMLSegmentationWithPreviewTool::SetNodeProperties(LabelSetImage::Pointer newMLPreview) -{ - if (newMLPreview.IsNotNull()) - { - this->m_MLPreviewNode->SetData(newMLPreview); - this->m_MLPreviewNode->SetProperty("binary", mitk::BoolProperty::New(false)); - mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); - renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); - this->m_MLPreviewNode->SetProperty("Image Rendering.Mode", renderingMode); - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); - vtkSmartPointer lookupTable = vtkSmartPointer::New(); - lookupTable->SetHueRange(1.0, 0.0); - lookupTable->SetSaturationRange(1.0, 1.0); - lookupTable->SetValueRange(1.0, 1.0); - lookupTable->SetTableRange(-1.0, 1.0); - lookupTable->Build(); - lut->SetVtkLookupTable(lookupTable); - prop->SetLookupTable(lut); - this->m_MLPreviewNode->SetProperty("LookupTable", prop); - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); - mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, newMLPreview->GetStatistics()->GetScalarValueMax()); - levWinProp->SetLevelWindow(levelwindow); - this->m_MLPreviewNode->SetProperty("levelwindow", levWinProp); - } -} - -void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) -{ - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - - if (nullptr == m_MLPreviewNode->GetData() - || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() - || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic - //segmentations have to compute a preview - //for all time steps on confirmation - || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg - //previews work with dynamic images - //with avoiding unnecessary other computations - ) - { - if (nullptr == inputAtTimeStep) - { - MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; - return; - } - - this->m_LastMLTimeStep = timeStep; - - auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); - this->SetNodeProperties(newMLPreview); - } - - if (!m_SelectedLabels.empty()) - { - const auto mlPreviewImage = this->GetMLPreview(); - if (nullptr != mlPreviewImage) - { - AccessByItk_n(mlPreviewImage, CalculateMergedSimplePreview, (previewImage, timeStep)); - } - } -} - -template -void mitk::AutoMLSegmentationWithPreviewTool::CalculateMergedSimplePreview(const itk::Image* itkImage, mitk::Image* segmentation, unsigned int timeStep) -{ - typedef itk::Image InputImageType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter FilterType; - - typename FilterType::Pointer filter = FilterType::New(); - - // InputImageType::Pointer itkImage; - typename OutputImageType::Pointer itkBinaryResultImage; - - filter->SetInput(itkImage); - filter->SetLowerThreshold(m_SelectedLabels[0]); - filter->SetUpperThreshold(m_SelectedLabels[0]); - filter->SetInsideValue(this->GetUserDefinedActiveLabel()); - filter->SetOutsideValue(0); - filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - filter->Update(); - itkBinaryResultImage = filter->GetOutput(); - itkBinaryResultImage->DisconnectPipeline(); - - // if more than one region id is used compute the union of all given binary regions - for (const auto labelID : m_SelectedLabels) - { - if (labelID != m_SelectedLabels[0]) - { - filter->SetLowerThreshold(labelID); - filter->SetUpperThreshold(labelID); - filter->SetInsideValue(this->GetUserDefinedActiveLabel()); - filter->SetOutsideValue(0); - filter->Update(); - - typename OutputImageType::Pointer tempImage = filter->GetOutput(); - - typename itk::OrImageFilter::Pointer orFilter = - itk::OrImageFilter::New(); - orFilter->SetInput1(tempImage); - orFilter->SetInput2(itkBinaryResultImage); - orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - - orFilter->UpdateLargestPossibleRegion(); - itkBinaryResultImage = orFilter->GetOutput(); - } - } - //---------------------------------------------------------------------------------------------------- - - segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); -} diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h deleted file mode 100644 index 60921765c6..0000000000 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ /dev/null @@ -1,82 +0,0 @@ -/*============================================================================ - -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 MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H -#define MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H - -#include "mitkAutoSegmentationWithPreviewTool.h" -#include "mitkDataNode.h" -#include "mitkLabelSetImage.h" - -#include - -namespace mitk -{ - /** - \brief Base class for any auto segmentation tool that provides a preview of the new segmentation and generates - segmentations with multiple labels. - - This tool class implements the basic logic to handle previews of multi label segmentations and - to allow to pick arbitrary labels as selected and merge them to a single segmentation to store this - segmentation as confirmed segmentation. - - \ingroup ToolManagerEtAl - \sa mitk::Tool - \sa QmitkInteractiveSegmentation - */ - class MITKSEGMENTATION_EXPORT AutoMLSegmentationWithPreviewTool : public AutoSegmentationWithPreviewTool - { - public: - mitkClassMacro(AutoMLSegmentationWithPreviewTool, AutoSegmentationWithPreviewTool); - - void Activated() override; - void Deactivated() override; - - using SelectedLabelVectorType = std::vector; - void SetSelectedLabels(const SelectedLabelVectorType& regions); - SelectedLabelVectorType GetSelectedLabels() const; - - const LabelSetImage* GetMLPreview() const; - - protected: - AutoMLSegmentationWithPreviewTool(); - ~AutoMLSegmentationWithPreviewTool() = default; - - void UpdateCleanUp() override; - void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - virtual void SetNodeProperties(LabelSetImage::Pointer); - /** Function to generate the new multi lable preview for a given time step input image. - * The function must be implemented by derived tools. - * This function is called by DoUpdatePreview if needed. - * Reasons are: - * - ML preview does not exist - * - Modify time of tools is newer then of ML preview - * - ML preview was not generated for the current selected timestep of input image or for the current selected timepoint.*/ - virtual LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) = 0; - - private: - /** Function to generate a simple (single lable) preview by merging all labels of the ML preview that are selected and - * copies that single label preview to the passed previewImage. - * This function is called by DoUpdatePreview if needed. - * @param mlPreviewImage Multi label preview that is the source. - * @param previewImage Pointer to the single label preview image that should receive the merged selected labels. - * @timeStep Time step of the previewImage that should be filled.*/ - template - void CalculateMergedSimplePreview(const itk::Image* mlImage, mitk::Image* segmentation, unsigned int timeStep); - - SelectedLabelVectorType m_SelectedLabels = {}; - - // holds the multilabel result as a preview image - mitk::DataNode::Pointer m_MLPreviewNode; - TimeStepType m_LastMLTimeStep = 0; - }; -} -#endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp index 11f25be299..772472b080 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -1,709 +1,730 @@ /*============================================================================ 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 "mitkAutoSegmentationWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() { } void mitk::AutoSegmentationWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; } void mitk::AutoSegmentationWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; } void mitk::AutoSegmentationWithPreviewTool::SetLabelTransferMode(LabelTransferMode LabelTransferMode) { m_LabelTransferMode = LabelTransferMode; } void mitk::AutoSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; } bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } void mitk::AutoSegmentationWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &AutoSegmentationWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... auto* activeLayer = newPreviewImage->GetActiveLabelSet(); auto* activeLabel = activeLayer->GetActiveLabel(); activeLabel->SetColor(previewColor); activeLayer->UpdateLookupTable(activeLabel->GetValue()); activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } std::vector > mitk::AutoSegmentationWithPreviewTool::GetLabelMapping() const { std::vector > labelMapping = { { this->GetUserDefinedActiveLabel(),this->GetUserDefinedActiveLabel() } }; if (LabelTransferMode::SelectedLabels == this->m_LabelTransferMode) { labelMapping.clear(); for (auto label : this->m_SelectedLabels) { labelMapping.push_back({ label, label }); } } else if (LabelTransferMode::AllLabels == this->m_LabelTransferMode) { labelMapping.clear(); const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); } } return labelMapping; } void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); auto labelMapping = this->GetLabelMapping(); TransferLabelContent(sourceLSImage, destLSImage, labelMapping, m_MergeStyle, m_OverwriteStyle, timeStep); } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { - mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." + mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } this->TransferPrepare(); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } bool mitk::AutoSegmentationWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback image. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback image feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } +void mitk::AutoSegmentationWithPreviewTool::TransferLabelInformation(std::vector>& labelMapping, + const mitk::LabelSetImage* source, mitk::LabelSetImage* target) +{ + for (const auto& [sourceLabel, targetLabel] : labelMapping) + { + if (!target->ExistLabel(targetLabel, target->GetActiveLayer())) + { + if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) + { + mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; + } + + auto clonedLabel = source->GetLabel(sourceLabel, source->GetActiveLayer())->Clone(); + clonedLabel->SetValue(targetLabel); + target->GetActiveLabelSet()->AddLabel(clonedLabel); + } + } +} + void mitk::AutoSegmentationWithPreviewTool::TransferPrepare() { auto labelMapping = this->GetLabelMapping(); DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); - for (const auto& [previewLabel, targetLabel] : labelMapping) - { - if (!resultSegmentation->ExistLabel(targetLabel, resultSegmentation->GetActiveLayer())) - { - if (!preview->ExistLabel(previewLabel, resultSegmentation->GetActiveLayer())) - { - mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << previewLabel; - } - - auto clonedLabel = preview->GetLabel(previewLabel, preview->GetActiveLayer())->Clone(); - clonedLabel->SetValue(targetLabel); - resultSegmentation->GetActiveLabelSet()->AddLabel(clonedLabel); - } - } + TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } const char* mitk::AutoSegmentationWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::AutoSegmentationWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::AutoSegmentationWithPreviewTool::GetImageByTimeStep(mitk::Image* image, unsigned int timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::AutoSegmentationWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimeStep(image, timePoint); } void mitk::AutoSegmentationWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); if (!this->GetToolManager()->GetDataStorage()->Exists(targetNode)) { this->GetToolManager()->GetDataStorage()->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::AutoSegmentationWithPreviewTool::GetCurrentSegmentationName() { if (this->GetToolManager()->GetWorkingData(0)) return this->GetToolManager()->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } + +void mitk::AutoSegmentationWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) +{ + mitk::ImageReadAccessor newMitkImgAcc(source); + + std::vector > labelMapping; + const auto labelSet = source->GetActiveLabelSet(); + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); + } + TransferLabelInformation(labelMapping, source, target); + + target->SetVolume(newMitkImgAcc.GetData(), timeStep); +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h index bedf9a612b..849948f4f4 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -1,279 +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. ============================================================================*/ #ifndef mitkAutoSegmentationWithPreviewTool_h_Included #define mitkAutoSegmentationWithPreviewTool_h_Included #include "mitkTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public Tool { public: mitkClassMacro(AutoSegmentationWithPreviewTool, Tool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); itkSetMacro(ResetsToEmptyPreview, bool); itkGetMacro(ResetsToEmptyPreview, bool); itkBooleanMacro(ResetsToEmptyPreview); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle); itkGetMacro(MergeStyle, MultiLabelSegmentation::MergeStyle); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle); itkGetMacro(OverwriteStyle, MultiLabelSegmentation::OverwriteStyle); enum class LabelTransferMode { ActiveLabel, //Only the active label will be transfered from preview to segmentation. SelectedLabels, //The labels defined as selected labels will be transfered. AllLabels //Transfer all labels of the preview }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferMode(LabelTransferMode LabelTransferMode); itkGetMacro(LabelTransferMode, LabelTransferMode); using SelectedLabelVectorType = std::vector; /** Specifies the labels that should be transfered form preview to the working image, if the segmentation is confirmed. The setting will be used, if LabelTransferMode is set to "SelectedLabels".*/ void SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer); itkGetMacro(SelectedLabels, SelectedLabelVectorType); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ bool IsUpdating() const; /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Returns the currently selected segmentation node * @return a mitk::DataNode which contains a segmentation image */ virtual DataNode* GetTargetSegmentationNode() const; /** Returns the image that contains the preview of the current segmentation. * Returns null if the node is not set or does not contain an image.*/ LabelSetImage* GetPreviewSegmentation(); const LabelSetImage* GetPreviewSegmentation() const; DataNode* GetPreviewSegmentationNode(); protected: ToolCommand::Pointer m_ProgressCommand; AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~AutoSegmentationWithPreviewTool() override; const char* GetGroup() const override; /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::Pointer GetImageByTimeStep(Image* image, unsigned int timestep); /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); void EnsureTargetSegmentationNodeInDataStorage() const; /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); /** This member function offers derived classes the possibility to alter what should happen directly before the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdatePrepare(); /** This member function offers derived classes the possibility to alter what should happen directly after the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdateCleanUp(); /** This member function offers derived classes the possibility to alter what should happen directly before the content of the preview is transfered to the segmentation, when the segmentation is confirmed. It is called by CreateResultSegmentationFromPreview. Default implementation ensure that all labels that will be transfered, exist in the segmentation. If they are not existing before the transfer, the will be added by cloning the label information of the preview.*/ virtual void TransferPrepare(); + static void TransferLabelInformation(std::vector>& labelMapping, + const mitk::LabelSetImage* source, mitk::LabelSetImage* target); + + /**Helper function that can be used to move the content of an LabelSetImage (the pixels of the active source layer and the labels). + This is e.g. helpfull if you generate an LabelSetImage content in DoUpdatePreview and you want to transfer it into the preview image.*/ + static void TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep); + /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. * It also provides the current/old segmentation at the time point, * which can be used, if the preview depends on the the segmenation so far. */ virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) = 0; /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool @remark Calling this function will generate a new preview image, and the old might be invalidated. Therefore this function should not be used within the scope of UpdatePreview (m_IsUpdating == true).*/ void ResetPreviewNode(); /** Resets the complete content of the preview image. The instance of the preview image and its settings * stay the same.*/ void ResetPreviewContent(); /** Resets only the image content of the specified timeStep of the preview image. If the preview image or the specified time step does not exist, nothing happens.*/ void ResetPreviewContentAtTimeStep(unsigned int timeStep); TimePointType GetLastTimePointOfUpdate() const; itkGetConstMacro(UserDefinedActiveLabel, Label::PixelType); itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /**Internal helper that ensures that the stored active label is up to date. This is a fix for T28131 / T28986. It should be refactored if T28524 is being worked on. On the long run, the active label will be communicated/set by the user/toolmanager as a state of the tool and the tool should react accordingly (like it does for other external state changes). @return indicates if the label has changed (true) or not. */ bool EnsureUpToDateUserDefinedActiveLabel(); std::vector > GetLabelMapping() const; /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; /** Controls if ResetPreviewNode generates an empty content (true) or clones the current segmentation (false).*/ bool m_ResetsToEmptyPreview = false; TimePointType m_LastTimePointOfUpdate = 0.; bool m_IsUpdating = false; Label::PixelType m_UserDefinedActiveLabel = 1; /** This variable indicates if for the tool a working plane geometry is defined. * If a working plane is defined the tool will only work an the slice of the input * and the segmentation. Thus only the relevant input slice will be passed to * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when * ConfirmSegmentation() is called.*/ PlaneGeometry::Pointer m_WorkingPlaneGeometry; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::MergeStyle. */ MultiLabelSegmentation::MergeStyle m_MergeStyle = MultiLabelSegmentation::MergeStyle::Replace; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::OverwriteStyle. */ MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = MultiLabelSegmentation::OverwriteStyle::RegardLocks; LabelTransferMode m_LabelTransferMode = LabelTransferMode::ActiveLabel; SelectedLabelVectorType m_SelectedLabels = {}; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index ec02f6a346..49e9271c64 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,86 +1,87 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; + this->SetLabelTransferMode(LabelTransferMode::AllLabels); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } const char* mitk::OtsuTool3D::GetName() const { return "Otsu"; } -mitk::LabelSetImage::Pointer mitk::OtsuTool3D::ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType /*timeStep*/) +void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { int numberOfThresholds = m_NumberOfRegions - 1; mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(m_UseValley); otsuFilter->SetNumberOfBins(m_NumberOfBins); otsuFilter->SetInput(inputAtTimeStep); otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } auto otsuResultImage = mitk::LabelSetImage::New(); otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); - return otsuResultImage; + TransferLabelSetImageContent(otsuResultImage, previewImage, timeStep); } unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index 3703b237a6..51c272d28c 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,64 +1,64 @@ /*============================================================================ 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 MITKOTSUTOOL3D_H #define MITKOTSUTOOL3D_H -#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; - class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoMLSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(OtsuTool3D, AutoMLSegmentationWithPreviewTool); + mitkClassMacro(OtsuTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; itkSetMacro(NumberOfBins, unsigned int); itkGetConstMacro(NumberOfBins, unsigned int); itkSetMacro(NumberOfRegions, unsigned int); itkGetConstMacro(NumberOfRegions, unsigned int); itkSetMacro(UseValley, bool); itkGetConstMacro(UseValley, bool); itkBooleanMacro(UseValley); /**Returns the number of max bins based on the current input image.*/ unsigned int GetMaxNumberOfBins() const; protected: OtsuTool3D() = default; ~OtsuTool3D() = default; - LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; unsigned int m_NumberOfBins = 128; unsigned int m_NumberOfRegions = 2; bool m_UseValley = false; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index 99779403d0..6ef6038caa 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,327 +1,326 @@ /*============================================================================ 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 "mitknnUnetTool.h" #include "mitkIOUtil.h" #include "mitkProcessExecutor.h" #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); } mitk::nnUNetTool::nnUNetTool() { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } mitk::nnUNetTool::~nnUNetTool() { itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); } void mitk::nnUNetTool::Activated() { Superclass::Activated(); + this->SetLabelTransferMode(LabelTransferMode::AllLabels); } void mitk::nnUNetTool::RenderOutputBuffer() { - if (m_OutputBuffer != nullptr) - { - Superclass::SetNodeProperties(m_OutputBuffer); - try - { - if (nullptr != this->GetPreviewSegmentationNode()) - { - this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); - } - if (this->GetSelectedLabels().empty()) - { - this->ResetPreviewNode(); - } - } - catch (const mitk::Exception &e) - { - MITK_INFO << e.GetDescription(); - } - } + //if (m_OutputBuffer != nullptr) + //{ + // Superclass::SetNodeProperties(m_OutputBuffer); + // try + // { + // if (nullptr != this->GetPreviewSegmentationNode()) + // { + // this->GetPreviewSegmentationNode()->SetVisibility(!this->GetSelectedLabels().empty()); + // } + // if (this->GetSelectedLabels().empty()) + // { + // this->ResetPreviewNode(); + // } + // } + // catch (const mitk::Exception &e) + // { + // MITK_INFO << e.GetDescription(); + // } + //} } void mitk::nnUNetTool::SetOutputBuffer(LabelSetImage::Pointer segmentation) { m_OutputBuffer = segmentation; } mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() { return m_OutputBuffer; } void mitk::nnUNetTool::ClearOutputBuffer() { m_OutputBuffer = nullptr; } us::ModuleResource mitk::nnUNetTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI_48x48.png"); return resource; } const char **mitk::nnUNetTool::GetXPM() const { return nullptr; } const char *mitk::nnUNetTool::GetName() const { return "nnUNet"; } mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() { return this->GetToolManager()->GetDataStorage(); } mitk::DataNode *mitk::nnUNetTool::GetRefNode() { return this->GetToolManager()->GetReferenceData(0); } namespace { void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } } // namespace -mitk::LabelSetImage::Pointer mitk::nnUNetTool::ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType /*timeStep*/) +void mitk::nnUNetTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (m_InputBuffer == inputAtTimeStep) { - return m_OutputBuffer; + return; } + std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); ProcessExecutor::ArgumentListType args; inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, m_TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); if (this->GetNoPip()) { scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; } try { if (this->GetMultiModal()) { const std::string fileFormat(".nii.gz"); const std::string fileNamePart("_000_000"); std::string outModalFile; size_t len = inDir.length() + 1 + token.length() + fileNamePart.length() + 1 + fileFormat.length(); outModalFile.reserve(len); // The 1(s) indicates a directory separator char and an underscore. for (size_t i = 0; i < m_OtherModalPaths.size(); ++i) { mitk::Image::ConstPointer modalImage = m_OtherModalPaths[i]; outModalFile.append(inDir); outModalFile.push_back(IOUtil::GetDirectorySeparator()); outModalFile.append(token); outModalFile.append(fileNamePart); outModalFile.append(std::to_string(i)); outModalFile.append(fileFormat); IOUtil::Save(modalImage.GetPointer(), outModalFile); outModalFile.clear(); } } else { IOUtil::Save(inputAtTimeStep, inputImagePath); } } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); - return nullptr; + return; } // Code calls external process std::string command = "nnUNet_predict"; if (this->GetNoPip()) { #ifdef _WIN32 command = "python"; #else command = "python3"; #endif } for (ModelParams &modelparam : m_ParamQ) { outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; modelparam.outputDir = outDir; args.clear(); if (this->GetNoPip()) { args.push_back(scriptPath); } args.push_back("-i"); args.push_back(inDir); args.push_back("-o"); args.push_back(outDir); args.push_back("-t"); args.push_back(modelparam.task); if (modelparam.model.find("cascade") != std::string::npos) { args.push_back("-ctr"); } else { args.push_back("-tr"); } args.push_back(modelparam.trainer); args.push_back("-m"); args.push_back(modelparam.model); args.push_back("-p"); args.push_back(modelparam.planId); if (!modelparam.folds.empty()) { args.push_back("-f"); for (auto fold : modelparam.folds) { args.push_back(fold); } } args.push_back("--num_threads_nifti_save"); args.push_back("1"); // fixing to 1 if (!this->GetMirror()) { args.push_back("--disable_tta"); } if (!this->GetMixedPrecision()) { args.push_back("--disable_mixed_precision"); } if (this->GetEnsemble()) { args.push_back("--save_npz"); } try { std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); itksys::SystemTools::PutEnv(cudaEnv.c_str()); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); - return nullptr; + return; } } if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) { args.clear(); command = "nnUNet_ensemble"; outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; args.push_back("-f"); for (ModelParams &modelparam : m_ParamQ) { args.push_back(modelparam.outputDir); } args.push_back("-o"); args.push_back(outDir); if (!this->GetPostProcessingJsonDirectory().empty()) { args.push_back("-pp"); args.push_back(this->GetPostProcessingJsonDirectory()); } spExec->Execute(this->GetPythonPath(), command, args); } try { - LabelSetImage::Pointer resultImage = LabelSetImage::New(); Image::Pointer outputImage = IOUtil::Load(outputImagePath); - resultImage->InitializeByLabeledImage(outputImage); - resultImage->SetGeometry(inputAtTimeStep->GetGeometry()); + previewImage->InitializeByLabeledImage(outputImage); m_InputBuffer = inputAtTimeStep; - m_OutputBuffer = resultImage; - return resultImage; + m_OutputBuffer = previewImage; } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); - return nullptr; + return; } } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.h b/Modules/Segmentation/Interactions/mitknnUnetTool.h index 7e0fcffd7a..dd165b7028 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.h +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.h @@ -1,213 +1,213 @@ /*============================================================================ 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 mitknnUnetTool_h_Included #define mitknnUnetTool_h_Included -#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkCommon.h" #include "mitkToolManager.h" #include #include #include #include namespace us { class ModuleResource; } namespace mitk { /** * @brief nnUNet parameter request object holding all model parameters for input. * Also holds output temporary directory path. */ struct ModelParams { std::string task; std::vector folds; std::string model; std::string trainer; std::string planId; std::string outputDir; std::string inputName; std::string timeStamp; size_t generateHash() const { std::string toHash; std::string foldsConcatenated = std::accumulate(folds.begin(), folds.end(), std::string("")); toHash += this->task; toHash += this->model; toHash += this->inputName; toHash += foldsConcatenated; toHash += this->timeStamp; size_t hashVal = std::hash{}(toHash); return hashVal; } }; /** \brief nnUNet segmentation tool. \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoMLSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT nnUNetTool : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(nnUNetTool, AutoMLSegmentationWithPreviewTool); + mitkClassMacro(nnUNetTool, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; void Activated() override; itkSetMacro(nnUNetDirectory, std::string); itkGetConstMacro(nnUNetDirectory, std::string); itkSetMacro(ModelDirectory, std::string); itkGetConstMacro(ModelDirectory, std::string); itkSetMacro(PythonPath, std::string); itkGetConstMacro(PythonPath, std::string); itkSetMacro(MitkTempDir, std::string); itkGetConstMacro(MitkTempDir, std::string); itkSetMacro(PostProcessingJsonDirectory, std::string); itkGetConstMacro(PostProcessingJsonDirectory, std::string); itkSetMacro(MixedPrecision, bool); itkGetConstMacro(MixedPrecision, bool); itkBooleanMacro(MixedPrecision); itkSetMacro(Mirror, bool); itkGetConstMacro(Mirror, bool); itkBooleanMacro(Mirror); itkSetMacro(MultiModal, bool); itkGetConstMacro(MultiModal, bool); itkBooleanMacro(MultiModal); itkSetMacro(NoPip, bool); itkGetConstMacro(NoPip, bool); itkBooleanMacro(NoPip); itkSetMacro(Ensemble, bool); itkGetConstMacro(Ensemble, bool); itkBooleanMacro(Ensemble); itkSetMacro(Predict, bool); itkGetConstMacro(Predict, bool); itkBooleanMacro(Predict); itkSetMacro(GpuId, unsigned int); itkGetConstMacro(GpuId, unsigned int); /** * @brief vector of ModelParams. * Size > 1 only for ensemble prediction. */ std::vector m_ParamQ; /** * @brief Holds paths to other input image modalities. * */ std::vector m_OtherModalPaths; mitk::Image::ConstPointer m_InputBuffer; /** * @brief Renders the output LabelSetImage. * To called in the main thread. */ void RenderOutputBuffer(); /** * @brief Get the Output Buffer object * * @return LabelSetImage::Pointer */ LabelSetImage::Pointer GetOutputBuffer(); /** * @brief Sets the outputBuffer to nullptr * */ void ClearOutputBuffer(); /** * @brief Returns the DataStorage from the ToolManager */ mitk::DataStorage *GetDataStorage(); mitk::DataNode *GetRefNode(); void SetOutputBuffer(LabelSetImage::Pointer); protected: /** * @brief Construct a new nnUNet Tool object and temp directory. * */ nnUNetTool(); /** * @brief Destroy the nnUNet Tool object and deletes the temp directory. * */ ~nnUNetTool(); /** * @brief Overriden method from the tool manager to execute the segmentation * Implementation: * 1. Saves the inputAtTimeStep in a temporary directory. * 2. Copies other modalities, renames and saves in the temporary directory, if required. * 3. Sets RESULTS_FOLDER and CUDA_VISIBLE_DEVICES variables in the environment. * 3. Iterates through the parameter queue (m_ParamQ) and executes "nnUNet_predict" command with the parameters * 4. Expects an output image to be saved in the temporary directory by the python proces. Loads it as * LabelSetImage and returns. * * @param inputAtTimeStep * @param timeStep * @return LabelSetImage::Pointer */ - LabelSetImage::Pointer ComputeMLPreview(const Image *inputAtTimeStep, TimeStepType timeStep) override; + virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep); private: std::string m_MitkTempDir; std::string m_nnUNetDirectory; std::string m_ModelDirectory; std::string m_PythonPath; std::string m_PostProcessingJsonDirectory; // bool m_UseGPU; kept for future // bool m_AllInGPU; bool m_MixedPrecision; bool m_Mirror; bool m_NoPip; bool m_MultiModal; bool m_Ensemble = false; bool m_Predict; LabelSetImage::Pointer m_OutputBuffer; unsigned int m_GpuId; const std::string m_TEMPLATE_FILENAME = "XXXXXX_000_0000.nii.gz"; }; } // namespace mitk #endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index fd1107c5e9..2440a1fc11 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,108 +1,107 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationWithPreviewTool.cpp - Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Interactions/mitkProcessExecutor.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png AI_48x48.png AI_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/PickingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp index 863e753c08..5ea2108fb0 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp @@ -1,89 +1,157 @@ /*============================================================================ 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 "QmitkAutoMLSegmentationToolGUIBase.h" -#include "mitkAutoMLSegmentationWithPreviewTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" -#include +#include +#include QmitkAutoMLSegmentationToolGUIBase::QmitkAutoMLSegmentationToolGUIBase() : QmitkAutoSegmentationToolGUIBase(false) { auto enableMLSelectedDelegate = [this](bool enabled) { bool result = enabled; - auto tool = this->GetConnectedToolAs(); + auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { - result = !tool->GetSelectedLabels().empty() && enabled; + result = (tool->GetLabelTransferMode()==mitk::AutoSegmentationWithPreviewTool::LabelTransferMode::AllLabels || !tool->GetSelectedLabels().empty()) && enabled; } else { result = false; } return result; }; m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } void QmitkAutoMLSegmentationToolGUIBase::InitializeUI(QBoxLayout* mainLayout) { + auto radioTransferAll = new QRadioButton("Transfer all labels", this); + radioTransferAll->setToolTip("Transfer all preview label when confirmed."); + radioTransferAll->setChecked(true); + connect(radioTransferAll, &QAbstractButton::toggled, this, &QmitkAutoMLSegmentationToolGUIBase::OnRadioTransferAllClicked); + mainLayout->addWidget(radioTransferAll); + m_RadioTransferAll = radioTransferAll; + + auto radioTransferSelected = new QRadioButton("Transfer selected labels", this); + radioTransferSelected->setToolTip("Transfer the selected preview label when confirmed."); + radioTransferSelected->setChecked(false); + mainLayout->addWidget(radioTransferSelected); + m_RadioTransferSelected = radioTransferSelected; + m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); sizePolicy2.setHorizontalStretch(0); sizePolicy2.setVerticalStretch(0); sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); m_LabelSelectionList->setSizePolicy(sizePolicy2); m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); + m_LabelSelectionList->setVisible(false); mainLayout->addWidget(m_LabelSelectionList); - connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged); + this->OnRadioTransferAllClicked(true); + Superclass::InitializeUI(mainLayout); } void QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) { - auto tool = this->GetConnectedToolAs(); + auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { - mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; + mitk::AutoSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; for (const auto& label : selectedLabels) { labelIDs.push_back(label->GetValue()); } tool->SetSelectedLabels(labelIDs); - tool->UpdatePreview(); + this->ActualizePreviewLabelVisibility(); this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; } } +void QmitkAutoMLSegmentationToolGUIBase::ActualizePreviewLabelVisibility() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + auto preview = tool->GetPreviewSegmentation(); + if (nullptr != preview) + { + auto labelSet = preview->GetActiveLabelSet(); + auto selectedLabels = tool->GetSelectedLabels(); + + for (auto labelIter = labelSet->IteratorBegin(); labelIter != labelSet->IteratorEnd(); ++labelIter) + { + bool isVisible = tool->GetLabelTransferMode() == mitk::AutoSegmentationWithPreviewTool::LabelTransferMode::AllLabels + || (std::find(selectedLabels.begin(), selectedLabels.end(), labelIter->second->GetValue()) != selectedLabels.end()); + labelIter->second->SetVisible(isVisible); + labelSet->UpdateLookupTable(labelIter->second->GetValue()); + } + } + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } +} + +void QmitkAutoMLSegmentationToolGUIBase::OnRadioTransferAllClicked(bool checked) +{ + m_LabelSelectionList->setVisible(!checked); + + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + if (checked) + { + tool->SetLabelTransferMode(mitk::AutoSegmentationWithPreviewTool::LabelTransferMode::AllLabels); + } + else + { + tool->SetLabelTransferMode(mitk::AutoSegmentationWithPreviewTool::LabelTransferMode::SelectedLabels); + } + } + this->ActualizePreviewLabelVisibility(); +} + void QmitkAutoMLSegmentationToolGUIBase::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->setEnabled(enabled); } + + if (nullptr != m_RadioTransferAll) + { + m_RadioTransferAll->setEnabled(enabled); + } + + if (nullptr != m_RadioTransferSelected) + { + m_RadioTransferSelected->setEnabled(enabled); + } } void QmitkAutoMLSegmentationToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) { if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->SetLabelSetImage(preview); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h index e4e48dc867..502d1321a6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h @@ -1,52 +1,56 @@ /*============================================================================ 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 QmitkAutoMLSegmentationToolGUIBase_h_Included #define QmitkAutoMLSegmentationToolGUIBase_h_Included #include "QmitkAutoSegmentationToolGUIBase.h" #include "QmitkSimpleLabelSetListWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for tools based on mitk::AutoMLSegmentationWithPreviewTool. This GUI offers an additional list to select the label that should be confirmed. */ class MITKSEGMENTATIONUI_EXPORT QmitkAutoMLSegmentationToolGUIBase : public QmitkAutoSegmentationToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkAutoMLSegmentationToolGUIBase, QmitkAutoSegmentationToolGUIBase); protected slots : void OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); + void OnRadioTransferAllClicked(bool checked); protected: QmitkAutoMLSegmentationToolGUIBase(); ~QmitkAutoMLSegmentationToolGUIBase() = default; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; void SetLabelSetPreview(const mitk::LabelSetImage* preview); + void ActualizePreviewLabelVisibility(); private: QmitkSimpleLabelSetListWidget* m_LabelSelectionList = nullptr; + QWidget* m_RadioTransferAll = nullptr; + QWidget* m_RadioTransferSelected = nullptr; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 6533d2e726..f6a24e8255 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,129 +1,145 @@ /*============================================================================ 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 "QmitkOtsuTool3DGUI.h" #include "mitkOtsuTool3D.h" #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkAutoMLSegmentationToolGUIBase() { + m_SuperclassEnableConfirmSegBtnFnc = m_EnableConfirmSegBtnFnc; + + auto enableMLSelectedDelegate = [this](bool enabled) + { + if (this->m_FirstPreviewComputation) + { + return false; + } + else + { + return this->m_SuperclassEnableConfirmSegBtnFnc(enabled); + } + }; + + m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); m_FirstPreviewComputation = true; } void QmitkOtsuTool3DGUI::InitializeUI(QBoxLayout* mainLayout) { m_Controls.setupUi(this); mainLayout->addLayout(m_Controls.verticalLayout); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); Superclass::InitializeUI(mainLayout); } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); auto tool = this->GetConnectedToolAs(); if (toggled && nullptr != tool) { int max = tool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { if (!m_FirstPreviewComputation && (tool->GetNumberOfRegions() == static_cast(m_Controls.m_Spinbox->value()) && tool->GetUseValley() == m_Controls.m_ValleyCheckbox->isChecked() && tool->GetNumberOfBins() == static_cast(m_Controls.m_BinsSpinBox->value()))) return; m_FirstPreviewComputation = false; try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } tool->SetNumberOfRegions(static_cast(m_Controls.m_Spinbox->value())); tool->SetUseValley(m_Controls.m_ValleyCheckbox->isChecked()); tool->SetNumberOfBins(static_cast(m_Controls.m_BinsSpinBox->value())); tool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } - this->SetLabelSetPreview(tool->GetMLPreview()); + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); tool->IsTimePointChangeAwareOn(); + this->ActualizePreviewLabelVisibility(); } } void QmitkOtsuTool3DGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); m_Controls.m_ValleyCheckbox->setEnabled(enabled); m_Controls.binLabel->setEnabled(enabled); m_Controls.m_BinsSpinBox->setEnabled(enabled); m_Controls.previewButton->setEnabled(enabled); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index 55b7030ea5..f56d8c0b65 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,64 +1,65 @@ /*============================================================================ 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 QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkAutoMLSegmentationToolGUIBase.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::OtsuTool3D. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkAutoMLSegmentationToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkAutoMLSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnPreviewBtnClicked(); void OnRegionSpinboxChanged(int); private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() = default; void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; Ui_QmitkOtsuToolWidgetControls m_Controls; bool m_FirstPreviewComputation = true; + EnableConfirmSegBtnFunctionType m_SuperclassEnableConfirmSegBtnFnc; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp index 8d6f3215f9..10420dc0a4 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp @@ -1,182 +1,199 @@ /*============================================================================ 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 "QmitkSimpleLabelSetListWidget.h" #include "mitkMessage.h" #include -QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr) +QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr), m_emmiting(false) { QGridLayout* layout = new QGridLayout(this); this->setContentsMargins(0, 0, 0, 0); m_LabelList = new QListWidget(this); m_LabelList->setSelectionMode(QAbstractItemView::MultiSelection); m_LabelList->setResizeMode(QListView::Adjust); m_LabelList->setAutoScrollMargin(0); layout->addWidget(m_LabelList); connect(m_LabelList, SIGNAL(itemSelectionChanged()), this, SLOT(OnLabelSelectionChanged())); } QmitkSimpleLabelSetListWidget::~QmitkSimpleLabelSetListWidget() { if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection); OnLooseLabelSetConnection(); } } QmitkSimpleLabelSetListWidget::LabelVectorType QmitkSimpleLabelSetListWidget::SelectedLabels() const { auto selectedItems = m_LabelList->selectedItems(); LabelVectorType result; QList::Iterator it; for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { auto labelValue = (*it)->data(Qt::UserRole).toUInt(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); result.push_back(labelSet->GetLabel(labelValue)); } return result; } const mitk::LabelSetImage* QmitkSimpleLabelSetListWidget::GetLabelSetImage() const { return m_LabelSetImage; } void QmitkSimpleLabelSetListWidget::SetLabelSetImage(const mitk::LabelSetImage* image) { if (image != m_LabelSetImage) { if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); this->OnLooseLabelSetConnection(); } m_LabelSetImage = image; if (m_LabelSetImage.IsNotNull()) { m_LabelSetImage->BeforeChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); this->OnLayerChanged(); } } } void QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection() { if (m_LabelSetImage.IsNull()) return; auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); // Reset LabelSetWidget Events labelSet->AddLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->RemoveLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->ModifyLabelEvent -= mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); } void QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection() { if (m_LabelSetImage.IsNull()) return; auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); // Reset LabelSetWidget Events labelSet->AddLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->RemoveLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); labelSet->ModifyLabelEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); } void QmitkSimpleLabelSetListWidget::OnLayerChanged() { this->OnEstablishLabelSetConnection(); - this->ResetList(); - emit ActiveLayerChanged(); - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_emmiting) + { + this->ResetList(); + + this->m_emmiting = true; + emit ActiveLayerChanged(); + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_emmiting = false; + } } void QmitkSimpleLabelSetListWidget::OnLabelChanged() { - this->ResetList(); - emit ActiveLayerChanged(); - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_emmiting) + { + this->ResetList(); + + this->m_emmiting = true; + emit ActiveLayerChanged(); + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_emmiting = false; + } } void QmitkSimpleLabelSetListWidget::OnLabelSelectionChanged() { - emit SelectedLabelsChanged(this->SelectedLabels()); + if (!this->m_emmiting) + { + this->m_emmiting = true; + emit SelectedLabelsChanged(this->SelectedLabels()); + this->m_emmiting = false; + } } void QmitkSimpleLabelSetListWidget::ResetList() { m_LabelList->clear(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); auto iter = labelSet->IteratorConstBegin(); for (; iter != labelSet->IteratorConstEnd(); ++iter) { auto color = iter->second->GetColor(); QPixmap pixmap(10, 10); pixmap.fill(QColor(color[0] * 255, color[1] * 255, color[2] * 255)); QIcon icon(pixmap); QListWidgetItem* item = new QListWidgetItem(icon, QString::fromStdString(iter->second->GetName())); item->setData(Qt::UserRole, QVariant(iter->second->GetValue())); m_LabelList->addItem(item); } } void QmitkSimpleLabelSetListWidget::SetSelectedLabels(const LabelVectorType& selectedLabels) { for (int i = 0; i < m_LabelList->count(); ++i) { QListWidgetItem* item = m_LabelList->item(i); auto labelValue = item->data(Qt::UserRole).toUInt(); auto finding = std::find_if(selectedLabels.begin(), selectedLabels.end(), [labelValue](const mitk::Label* label) {return label->GetValue() == labelValue; }); item->setSelected(finding != selectedLabels.end()); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h index 88c716ff0c..3bce7e81ca 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h @@ -1,63 +1,64 @@ /*============================================================================ 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 QmitkSimpleLabelSetListWidget_h_Included #define QmitkSimpleLabelSetListWidget_h_Included #include "mitkLabel.h" #include "mitkLabelSetImage.h" #include #include /** \brief Widget that offers a simple list that displays all labels (color and name) in the active layer of a LabelSetImage. */ class MITKSEGMENTATIONUI_EXPORT QmitkSimpleLabelSetListWidget : public QWidget { Q_OBJECT public: QmitkSimpleLabelSetListWidget(QWidget* parent = nullptr); ~QmitkSimpleLabelSetListWidget() override; using LabelVectorType = std::vector; LabelVectorType SelectedLabels() const; const mitk::LabelSetImage* GetLabelSetImage() const; signals: void SelectedLabelsChanged(const LabelVectorType& selectedLabels); void ActiveLayerChanged(); public slots : void SetLabelSetImage(const mitk::LabelSetImage* image); void SetSelectedLabels(const LabelVectorType& selectedLabels); protected slots: void OnLabelSelectionChanged(); protected: void OnLayerChanged(); void OnLabelChanged(); void OnLooseLabelSetConnection(); void OnEstablishLabelSetConnection(); void ResetList(); mitk::LabelSetImage::ConstPointer m_LabelSetImage; QListWidget* m_LabelList; + bool m_emmiting; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp index 29114ac8f8..a0b9a00090 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -1,588 +1,588 @@ /*============================================================================ 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 "QmitknnUNetToolGUI.h" #include "mitkProcessExecutor.h" #include "mitknnUnetTool.h" #include #include #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkAutoMLSegmentationToolGUIBase() { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The nnUNet tool might not work."; ShowErrorMessage(warning); } // define predicates for multi modal data selection combobox auto imageType = mitk::TNodePredicateDataType::New(); auto labelSetImageType = mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New()); m_MultiModalPredicate = mitk::NodePredicateAnd::New(imageType, labelSetImageType).GetPointer(); } void QmitknnUNetToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); } void QmitknnUNetToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.pythonEnvComboBox->addItem("/usr/bin"); #endif m_Controls.pythonEnvComboBox->addItem("Select"); AutoParsePythonPaths(); SetGPUInfo(); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewRequested())); connect(m_Controls.modeldirectoryBox, SIGNAL(directoryChanged(const QString &)), this, SLOT(OnDirectoryChanged(const QString &))); connect( m_Controls.modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect(m_Controls.taskBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTaskChanged(const QString &))); connect( m_Controls.plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); connect(m_Controls.multiModalBox, SIGNAL(stateChanged(int)), this, SLOT(OnCheckBoxChanged(int))); connect(m_Controls.multiModalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnModalitiesNumberChanged(int))); connect(m_Controls.pythonEnvComboBox, #if QT_VERSION >= 0x050F00 // 5.15 SIGNAL(textActivated(const QString &)), #elif QT_VERSION >= 0x050C00 // 5.12 SIGNAL(currentTextChanged(const QString &)), #endif this, SLOT(OnPythonPathChanged(const QString &))); connect(m_Controls.refreshdirectoryBox, SIGNAL(clicked()), this, SLOT(OnRefreshPresssed())); connect(m_Controls.clearCacheButton, SIGNAL(clicked()), this, SLOT(OnClearCachePressed())); m_Controls.multiModalSpinBox->setVisible(false); m_Controls.multiModalSpinBox->setEnabled(false); m_Controls.multiModalSpinLabel->setVisible(false); m_Controls.previewButton->setEnabled(false); QIcon refreshIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/view-refresh.svg")); m_Controls.refreshdirectoryBox->setIcon(refreshIcon); QIcon dirIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg")); m_Controls.modeldirectoryBox->setIcon(dirIcon); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); if (m_GpuLoader.GetGPUCount() != 0) { WriteStatusMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } else { WriteErrorMessage(QString("STATUS: Welcome to nnUNet. " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected.")); } mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); m_UI_ROWS = m_Controls.advancedSettingsLayout->rowCount(); // Must do. Row count is correct only here. DisableEverything(); QString lastSelectedPyEnv = m_Settings.value("nnUNet/LastPythonPath").toString(); m_Controls.pythonEnvComboBox->setCurrentText(lastSelectedPyEnv); } void QmitknnUNetToolGUI::OnPreviewRequested() { mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); if (nullptr != tool) { QString pythonPathTextItem = ""; try { size_t hashKey(0); m_Controls.previewButton->setEnabled(false); // To prevent misclicked back2back prediction. qApp->processEvents(); tool->PredictOn(); // purposefully placed to make tool->GetMTime different than before. QString modelName = m_Controls.modelBox->currentText(); if (modelName.startsWith("ensemble", Qt::CaseInsensitive)) { ProcessEnsembleModelsParams(tool); } else { ProcessModelParams(tool); } pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); QString pythonPath = m_PythonPath; if (!IsNNUNetInstalled(pythonPath)) { throw std::runtime_error("nnUNet is not detected in the selected python environment. Please select a valid " "python environment or install nnUNet."); } tool->SetPythonPath(pythonPath.toStdString()); tool->SetModelDirectory(m_ParentFolder->getResultsFolder().toStdString()); // checkboxes tool->SetMirror(m_Controls.mirrorBox->isChecked()); tool->SetMixedPrecision(m_Controls.mixedPrecisionBox->isChecked()); tool->SetNoPip(false); bool doCache = m_Controls.enableCachingCheckBox->isChecked(); // Spinboxes tool->SetGpuId(FetchSelectedGPUFromUI()); // Multi-Modal tool->MultiModalOff(); if (m_Controls.multiModalBox->isChecked()) { if (m_Controls.multiModalSpinBox->value() > 0) { tool->m_OtherModalPaths.clear(); tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); tool->MultiModalOn(); } else { throw std::runtime_error("Please select more than one modalities for a multi-modal task. If you " "would like to use only one modality then uncheck the Multi-Modal option."); } } if (doCache) { hashKey = nnUNetCache::GetUniqueHash(tool->m_ParamQ); if (m_Cache.contains(hashKey)) { tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } } if (tool->GetPredict()) { tool->m_InputBuffer = nullptr; WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); tool->UpdatePreview(); if (nullptr == tool->GetOutputBuffer()) { SegmentationProcessFailed(); } else { SegmentationResultHandler(tool); if (doCache) { - AddToCache(hashKey, tool->GetMLPreview()); + AddToCache(hashKey, tool->GetPreviewSegmentation()); } } tool->PredictOff(); // purposefully placed to make tool->GetMTime different than before. } else { MITK_INFO << "won't do segmentation. Key found: " << QString::number(hashKey).toStdString(); if (m_Cache.contains(hashKey)) { nnUNetCache *cacheObject = m_Cache[hashKey]; MITK_INFO << "fetched pointer " << cacheObject->m_SegCache.GetPointer(); tool->SetOutputBuffer(const_cast(cacheObject->m_SegCache.GetPointer())); SegmentationResultHandler(tool, true); } } m_Controls.previewButton->setEnabled(true); } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for nnUNet segmentation. Reason: " << e.what(); ShowErrorMessage(errorMsg.str()); WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); return; } catch (...) { std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); return; } if (!pythonPathTextItem.isEmpty()) { // only cache if the prediction ended without errors. m_Settings.setValue("nnUNet/LastPythonPath", pythonPathTextItem); } } } std::vector QmitknnUNetToolGUI::FetchMultiModalImagesFromUI() { std::vector modals; if (m_Controls.multiModalBox->isChecked() && !m_Modalities.empty()) { std::set nodeNames; // set container for keeping names of all nodes to check if they are added twice. for (QmitkDataStorageComboBox *modality : m_Modalities) { if (nodeNames.find(modality->GetSelectedNode()->GetName()) == nodeNames.end()) { modals.push_back(dynamic_cast(modality->GetSelectedNode()->GetData())); nodeNames.insert(modality->GetSelectedNode()->GetName()); } else { throw std::runtime_error("Same modality is selected more than once. Please change your selection."); break; } } } return modals; } bool QmitknnUNetToolGUI::IsNNUNetInstalled(const QString &pythonPath) { QString fullPath = pythonPath; #ifdef _WIN32 if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); } #else if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); } #endif fullPath = fullPath.mid(fullPath.indexOf(" ") + 1); bool isExists = QFile::exists(fullPath + QDir::separator() + QString("nnUNet_predict")) && QFile::exists(fullPath + QDir::separator() + QString("python3")); return isExists; } void QmitknnUNetToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitknnUNetToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); } void QmitknnUNetToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); } void QmitknnUNetToolGUI::ProcessEnsembleModelsParams(mitk::nnUNetTool::Pointer tool) { if (m_EnsembleParams[0]->modelBox->currentText() == m_EnsembleParams[1]->modelBox->currentText()) { throw std::runtime_error("Both models you have selected for ensembling are the same."); } QString taskName = m_Controls.taskBox->currentText(); bool isPPJson = m_Controls.postProcessingCheckBox->isChecked(); std::vector requestQ; QString ppDirFolderNamePart1 = "ensemble_"; QStringList ppDirFolderNameParts; for (auto &layout : m_EnsembleParams) { QStringList ppDirFolderName; QString modelName = layout->modelBox->currentText(); ppDirFolderName << modelName; ppDirFolderName << "__"; QString trainer = layout->trainerBox->currentText(); ppDirFolderName << trainer; ppDirFolderName << "__"; QString planId = layout->plannerBox->currentText(); ppDirFolderName << planId; if (!IsModelExists(modelName, taskName, QString(trainer + "__" + planId))) { std::string errorMsg = "The configuration " + modelName.toStdString() + " you have selected doesn't exist. Check your Results Folder again."; throw std::runtime_error(errorMsg); } std::vector testfold = FetchSelectedFoldsFromUI(layout->foldBox); mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, testfold); requestQ.push_back(modelObject); ppDirFolderNameParts << ppDirFolderName.join(QString("")); } tool->EnsembleOn(); if (isPPJson) { QString ppJsonFilePossibility1 = QDir::cleanPath( m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.first() + "--" + ppDirFolderNameParts.last() + QDir::separator() + "postprocessing.json"); QString ppJsonFilePossibility2 = QDir::cleanPath( m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + "ensembles" + QDir::separator() + taskName + QDir::separator() + ppDirFolderNamePart1 + ppDirFolderNameParts.last() + "--" + ppDirFolderNameParts.first() + QDir::separator() + "postprocessing.json"); if (QFile(ppJsonFilePossibility1).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility1.toStdString()); const QString statusMsg = "Post Processing JSON file found: " + ppJsonFilePossibility1; WriteStatusMessage(statusMsg); } else if (QFile(ppJsonFilePossibility2).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); const QString statusMsg = "Post Processing JSON file found:" + ppJsonFilePossibility2; WriteStatusMessage(statusMsg); } else { std::string errorMsg = "No post processing file was found for the selected ensemble combination. Continuing anyway..."; ShowErrorMessage(errorMsg); } } tool->m_ParamQ.clear(); tool->m_ParamQ = requestQ; } void QmitknnUNetToolGUI::ProcessModelParams(mitk::nnUNetTool::Pointer tool) { tool->EnsembleOff(); std::vector requestQ; QString modelName = m_Controls.modelBox->currentText(); QString taskName = m_Controls.taskBox->currentText(); QString trainer = m_Controls.trainerBox->currentText(); QString planId = m_Controls.plannerBox->currentText(); std::vector fetchedFolds = FetchSelectedFoldsFromUI(m_Controls.foldBox); mitk::ModelParams modelObject = MapToRequest(modelName, taskName, trainer, planId, fetchedFolds); requestQ.push_back(modelObject); tool->m_ParamQ.clear(); tool->m_ParamQ = requestQ; } bool QmitknnUNetToolGUI::IsModelExists(const QString &modelName, const QString &taskName, const QString &trainerPlanner) { QString modelSearchPath = QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + modelName + QDir::separator() + taskName + QDir::separator() + trainerPlanner); if (QDir(modelSearchPath).exists()) { return true; } return false; } void QmitknnUNetToolGUI::CheckAllInCheckableComboBox(ctkCheckableComboBox *foldBox) { // Recalling all added items to check-mark it. const QAbstractItemModel *qaim = foldBox->checkableModel(); auto rows = qaim->rowCount(); for (std::remove_const_t i = 0; i < rows; ++i) { const QModelIndex mi = qaim->index(i, 0); foldBox->setCheckState(mi, Qt::Checked); } } std::pair QmitknnUNetToolGUI::ExtractTrainerPlannerFromString(QStringList trainerPlanners) { QString splitterString = "__"; QStringList trainers, planners; for (const auto &trainerPlanner : trainerPlanners) { trainers << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).first(); planners << trainerPlanner.split(splitterString, QString::SplitBehavior::SkipEmptyParts).last(); } trainers.removeDuplicates(); planners.removeDuplicates(); return std::make_pair(trainers, planners); } std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI(ctkCheckableComboBox *foldBox) { std::vector folds; if (foldBox->noneChecked()) { CheckAllInCheckableComboBox(foldBox); } QModelIndexList foldList = foldBox->checkedIndexes(); for (const auto &index : foldList) { QString foldQString = foldBox->itemText(index.row()).split("_", QString::SplitBehavior::SkipEmptyParts).last(); folds.push_back(foldQString.toStdString()); } return folds; } void QmitknnUNetToolGUI::OnClearCachePressed() { m_Cache.clear(); UpdateCacheCountOnUI(); } void QmitknnUNetToolGUI::UpdateCacheCountOnUI() { QString cacheText = m_CACHE_COUNT_BASE_LABEL + QString::number(m_Cache.size()); m_Controls.cacheCountLabel->setText(cacheText); } void QmitknnUNetToolGUI::AddToCache(size_t &hashKey, mitk::LabelSetImage::ConstPointer mlPreview) { nnUNetCache *newCacheObj = new nnUNetCache; newCacheObj->m_SegCache = mlPreview; m_Cache.insert(hashKey, newCacheObj); MITK_INFO << "New hash: " << hashKey << " " << newCacheObj->m_SegCache.GetPointer(); UpdateCacheCountOnUI(); } void QmitknnUNetToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitknnUNetToolGUI::FetchSelectedGPUFromUI() { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } QString QmitknnUNetToolGUI::FetchResultsFolderFromEnv() { const char *pathVal = itksys::SystemTools::GetEnv("RESULTS_FOLDER"); QString retVal; if (pathVal) { retVal = QString::fromUtf8(pathVal); } else { retVal = m_Settings.value("nnUNet/LastRESULTS_FOLDERPath").toString(); } return retVal; } QString QmitknnUNetToolGUI::DumpJSONfromPickle(const QString &parentPath) { const QString picklePath = parentPath + QDir::separator() + QString("plans.pkl"); const QString jsonPath = parentPath + QDir::separator() + QString("mitk_export.json"); if (!QFile::exists(jsonPath)) { mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("-c"); std::string pythonCode; // python syntax to parse plans.pkl file and export as Json file. pythonCode.append("import pickle;"); pythonCode.append("import json;"); pythonCode.append("loaded_pickle = pickle.load(open('"); pythonCode.append(picklePath.toStdString()); pythonCode.append("','rb'));"); pythonCode.append("modal_dict = {key: loaded_pickle[key] for key in loaded_pickle.keys() if key in " "['modalities','num_modalities']};"); pythonCode.append("json.dump(modal_dict, open('"); pythonCode.append(jsonPath.toStdString()); pythonCode.append("', 'w'))"); args.push_back(pythonCode); try { spExec->Execute(m_PythonPath.toStdString(), "python3", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Pickle parsing FAILED!" << e.GetDescription(); // SHOW ERROR WriteStatusMessage( "Parsing failed in backend. Multiple Modalities will now have to be manually entered by the user."); return QString(""); } } return jsonPath; } void QmitknnUNetToolGUI::DisplayMultiModalInfoFromJSON(const QString &jsonPath) { if (QFile::exists(jsonPath)) { QFile file(jsonPath); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QByteArray bytes = file.readAll(); file.close(); QJsonParseError jsonError; QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError); if (jsonError.error != QJsonParseError::NoError) { MITK_INFO << "fromJson failed: " << jsonError.errorString().toStdString() << endl; return; } if (document.isObject()) { QJsonObject jsonObj = document.object(); int num_mods = jsonObj["num_modalities"].toInt(); ClearAllModalLabels(); if (num_mods > 1) { m_Controls.multiModalBox->setChecked(true); m_Controls.multiModalBox->setEnabled(false); m_Controls.multiModalSpinBox->setValue(num_mods - 1); m_Controls.advancedSettingsLayout->update(); QJsonObject obj = jsonObj.value("modalities").toObject(); QStringList keys = obj.keys(); int count = 0; for (auto key : keys) { auto value = obj.take(key); QLabel *label = new QLabel("" + value.toString() + "", this); m_ModalLabels.push_back(label); m_Controls.advancedSettingsLayout->addWidget(label, m_UI_ROWS + 1 + count, 0); count++; } m_Controls.multiModalSpinBox->setMinimum(num_mods - 1); m_Controls.advancedSettingsLayout->update(); } else { m_Controls.multiModalSpinBox->setMinimum(0); m_Controls.multiModalBox->setChecked(false); } } } } } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp index 4b82f39ed2..1b1308af45 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolSlots.cpp @@ -1,478 +1,478 @@ #include "QmitknnUNetToolGUI.h" #include #include #include #include #include void QmitknnUNetToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitknnUNetToolGUI::ClearAllModalities() { m_Controls.multiModalSpinBox->setMinimum(0); m_Controls.multiModalBox->setChecked(false); ClearAllModalLabels(); } void QmitknnUNetToolGUI::ClearAllModalLabels() { for (auto modalLabel : m_ModalLabels) { delete modalLabel; // delete the layout item m_ModalLabels.pop_back(); } m_Controls.advancedSettingsLayout->update(); } void QmitknnUNetToolGUI::DisableEverything() { m_Controls.modeldirectoryBox->setEnabled(false); m_Controls.refreshdirectoryBox->setEnabled(false); m_Controls.previewButton->setEnabled(false); m_Controls.multiModalSpinBox->setVisible(false); m_Controls.multiModalBox->setEnabled(false); ClearAllComboBoxes(); ClearAllModalities(); } void QmitknnUNetToolGUI::ClearAllComboBoxes() { m_Controls.modelBox->clear(); m_Controls.taskBox->clear(); m_Controls.foldBox->clear(); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); for (auto &layout : m_EnsembleParams) { layout->modelBox->clear(); layout->trainerBox->clear(); layout->plannerBox->clear(); layout->foldBox->clear(); } } void QmitknnUNetToolGUI::OnRefreshPresssed() { const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); OnDirectoryChanged(resultsFolder); } void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) { m_Controls.previewButton->setEnabled(false); ClearAllComboBoxes(); ClearAllModalities(); m_ParentFolder = std::make_shared(resultsFolder); auto tasks = m_ParentFolder->getAllTasks(); tasks.removeDuplicates(); std::for_each(tasks.begin(), tasks.end(), [this](QString task) { m_Controls.taskBox->addItem(task); }); m_Settings.setValue("nnUNet/LastRESULTS_FOLDERPath", resultsFolder); } void QmitknnUNetToolGUI::OnModelChanged(const QString &model) { if (model.isEmpty()) { return; } ClearAllModalities(); auto selectedTask = m_Controls.taskBox->currentText(); ctkComboBox *box = qobject_cast(sender()); if (box == m_Controls.modelBox) { if (model == m_VALID_MODELS.last()) { m_Controls.trainerBox->setVisible(false); m_Controls.trainerLabel->setVisible(false); m_Controls.plannerBox->setVisible(false); m_Controls.plannerLabel->setVisible(false); m_Controls.foldBox->setVisible(false); m_Controls.foldLabel->setVisible(false); ShowEnsembleLayout(true); auto models = m_ParentFolder->getModelsForTask(m_Controls.taskBox->currentText()); models.removeDuplicates(); models.removeOne(m_VALID_MODELS.last()); for (auto &layout : m_EnsembleParams) { layout->modelBox->clear(); layout->trainerBox->clear(); layout->plannerBox->clear(); std::for_each(models.begin(), models.end(), [&layout, this](QString model) { if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) layout->modelBox->addItem(model); }); } m_Controls.previewButton->setEnabled(true); } else { m_Controls.trainerBox->setVisible(true); m_Controls.trainerLabel->setVisible(true); m_Controls.plannerBox->setVisible(true); m_Controls.plannerLabel->setVisible(true); m_Controls.foldBox->setVisible(true); m_Controls.foldLabel->setVisible(true); m_Controls.previewButton->setEnabled(false); ShowEnsembleLayout(false); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); std::for_each( trainers.begin(), trainers.end(), [this](QString trainer) { m_Controls.trainerBox->addItem(trainer); }); std::for_each( planners.begin(), planners.end(), [this](QString planner) { m_Controls.plannerBox->addItem(planner); }); } } else if (!m_EnsembleParams.empty()) { for (auto &layout : m_EnsembleParams) { if (box == layout->modelBox) { layout->trainerBox->clear(); layout->plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); std::for_each(trainers.begin(), trainers.end(), [&layout](const QString &trainer) { layout->trainerBox->addItem(trainer); }); std::for_each(planners.begin(), planners.end(), [&layout](const QString &planner) { layout->plannerBox->addItem(planner); }); break; } } } } void QmitknnUNetToolGUI::OnTaskChanged(const QString &task) { if (task.isEmpty()) { return; } m_Controls.modelBox->clear(); auto models = m_ParentFolder->getModelsForTask(task); models.removeDuplicates(); if (!models.contains(m_VALID_MODELS.last(), Qt::CaseInsensitive)) { models << m_VALID_MODELS.last(); // add ensemble even if folder doesn't exist } std::for_each(models.begin(), models.end(), [this](QString model) { if (m_VALID_MODELS.contains(model, Qt::CaseInsensitive)) m_Controls.modelBox->addItem(model); }); } void QmitknnUNetToolGUI::OnTrainerChanged(const QString &plannerSelected) { if (plannerSelected.isEmpty()) { return; } auto *box = qobject_cast(sender()); if (box == m_Controls.plannerBox) { m_Controls.foldBox->clear(); auto selectedTrainer = m_Controls.trainerBox->currentText(); auto selectedTask = m_Controls.taskBox->currentText(); auto selectedModel = m_Controls.modelBox->currentText(); auto folds = m_ParentFolder->getFoldsForTrainerPlanner( selectedTrainer, plannerSelected, selectedTask, selectedModel); std::for_each(folds.begin(), folds.end(), [this](QString fold) { if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet m_Controls.foldBox->addItem(fold); }); if (m_Controls.foldBox->count() != 0) { CheckAllInCheckableComboBox(m_Controls.foldBox); m_Controls.previewButton->setEnabled(true); const QString parentPath = QDir::cleanPath(m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + selectedModel + QDir::separator() + selectedTask + QDir::separator() + selectedTrainer + QString("__") + plannerSelected); const QString jsonPath = this->DumpJSONfromPickle(parentPath); if (!jsonPath.isEmpty()) { this->DisplayMultiModalInfoFromJSON(jsonPath); } } } else if (!m_EnsembleParams.empty()) { for (auto &layout : m_EnsembleParams) { if (box == layout->plannerBox) { layout->foldBox->clear(); auto selectedTrainer = layout->trainerBox->currentText(); auto selectedTask = m_Controls.taskBox->currentText(); auto selectedModel = layout->modelBox->currentText(); auto folds = m_ParentFolder->getFoldsForTrainerPlanner( selectedTrainer, plannerSelected, selectedTask, selectedModel); std::for_each(folds.begin(), folds.end(), [&layout](const QString &fold) { if (fold.startsWith("fold_", Qt::CaseInsensitive)) // imposed by nnUNet layout->foldBox->addItem(fold); }); if (layout->foldBox->count() != 0) { CheckAllInCheckableComboBox(layout->foldBox); m_Controls.previewButton->setEnabled(true); const QString parentPath = QDir::cleanPath( m_ParentFolder->getResultsFolder() + QDir::separator() + "nnUNet" + QDir::separator() + selectedModel + QDir::separator() + selectedTask + QDir::separator() + selectedTrainer + QString("__") + plannerSelected); const QString jsonPath = this->DumpJSONfromPickle(parentPath); if (!jsonPath.isEmpty()) { this->DisplayMultiModalInfoFromJSON(jsonPath); } } break; } } } } void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { OnPythonPathChanged(path); // recall same function for new path validation m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); } } else if (!IsNNUNetInstalled(pyEnv)) { std::string warning = "WARNING: nnUNet is not detected on the Python environment you selected. Please select another " "environment or create one. For more info refer https://github.com/MIC-DKFZ/nnUNet"; ShowErrorMessage(warning); DisableEverything(); } else { m_Controls.modeldirectoryBox->setEnabled(true); m_Controls.previewButton->setEnabled(true); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.multiModalBox->setEnabled(true); QString setVal = FetchResultsFolderFromEnv(); if (!setVal.isEmpty()) { m_Controls.modeldirectoryBox->setDirectory(setVal); } OnRefreshPresssed(); m_PythonPath = pyEnv.mid(pyEnv.indexOf(" ") + 1); if (!(m_PythonPath.endsWith("bin", Qt::CaseInsensitive) || m_PythonPath.endsWith("bin/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("bin"); } } } void QmitknnUNetToolGUI::OnCheckBoxChanged(int state) { bool visibility = false; if (state == Qt::Checked) { visibility = true; } ctkCheckBox *box = qobject_cast(sender()); if (box != nullptr) { if (box->objectName() == QString("multiModalBox")) { m_Controls.multiModalSpinLabel->setVisible(visibility); m_Controls.multiModalSpinBox->setVisible(visibility); if (visibility) { QmitkDataStorageComboBox *defaultImage = new QmitkDataStorageComboBox(this, true); defaultImage->setObjectName(QString("multiModal_" + QString::number(0))); defaultImage->SetPredicate(m_MultiModalPredicate); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); if (tool != nullptr) { defaultImage->SetDataStorage(tool->GetDataStorage()); defaultImage->SetSelectedNode(tool->GetRefNode()); } m_Controls.advancedSettingsLayout->addWidget(defaultImage, m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); m_Modalities.push_back(defaultImage); } else { OnModalitiesNumberChanged(0); m_Controls.multiModalSpinBox->setValue(0); delete m_Modalities[0]; m_Modalities.pop_back(); ClearAllModalLabels(); } } } } void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) { while (num > static_cast(m_Modalities.size() - 1)) { QmitkDataStorageComboBox *multiModalBox = new QmitkDataStorageComboBox(this, true); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); multiModalBox->SetDataStorage(tool->GetDataStorage()); multiModalBox->SetPredicate(m_MultiModalPredicate); multiModalBox->setObjectName(QString("multiModal_" + QString::number(m_Modalities.size() + 1))); m_Controls.advancedSettingsLayout->addWidget(multiModalBox, m_UI_ROWS + m_Modalities.size() + 1, 1, 1, 3); m_Modalities.push_back(multiModalBox); } while (num < static_cast(m_Modalities.size() - 1) && !m_Modalities.empty()) { QmitkDataStorageComboBox *child = m_Modalities.back(); if (child->objectName() == "multiModal_0") { std::iter_swap(m_Modalities.end() - 2, m_Modalities.end() - 1); child = m_Modalities.back(); } delete child; // delete the layout item m_Modalities.pop_back(); } m_Controls.advancedSettingsLayout->update(); } void QmitknnUNetToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.pythonEnvComboBox->insertItem(0, "(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { m_Controls.pythonEnvComboBox->insertItem(0, "(" + envName + "): " + subIt.filePath()); } } } m_Controls.pythonEnvComboBox->setCurrentIndex(-1); } mitk::ModelParams QmitknnUNetToolGUI::MapToRequest(const QString &modelName, const QString &taskName, const QString &trainer, const QString &planId, const std::vector &folds) { mitk::ModelParams requestObject; requestObject.model = modelName.toStdString(); requestObject.trainer = trainer.toStdString(); requestObject.planId = planId.toStdString(); requestObject.task = taskName.toStdString(); requestObject.folds = folds; mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); requestObject.inputName = tool->GetRefNode()->GetName(); requestObject.timeStamp = std::to_string(mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint()); return requestObject; } void QmitknnUNetToolGUI::SegmentationProcessFailed() { WriteErrorMessage( "STATUS: Error in the segmentation process.
No resulting segmentation can be loaded.
"); this->setCursor(Qt::ArrowCursor); std::stringstream stream; stream << "Error in the segmentation process. No resulting segmentation can be loaded."; ShowErrorMessage(stream.str()); } void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool, bool forceRender) { if (forceRender) { tool->RenderOutputBuffer(); } - this->SetLabelSetPreview(tool->GetMLPreview()); + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); WriteStatusMessage("STATUS: Segmentation task finished successfully.
If multiple Preview objects are selected to Confirm, " "they will be merged. Any unselected Preview objects will be lost.
"); } void QmitknnUNetToolGUI::ShowEnsembleLayout(bool visible) { if (m_EnsembleParams.empty()) { ctkCollapsibleGroupBox *groupBoxModel1 = new ctkCollapsibleGroupBox(this); auto lay1 = std::make_unique(groupBoxModel1); groupBoxModel1->setObjectName(QString::fromUtf8("model_1_Box")); groupBoxModel1->setTitle(QString::fromUtf8("Model 1")); groupBoxModel1->setMinimumSize(QSize(0, 0)); groupBoxModel1->setCollapsedHeight(5); groupBoxModel1->setCollapsed(false); groupBoxModel1->setFlat(true); groupBoxModel1->setAlignment(Qt::AlignRight); m_Controls.advancedSettingsLayout->addWidget(groupBoxModel1, 5, 0, 1, 2); connect(lay1->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay1->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay1)); ctkCollapsibleGroupBox *groupBoxModel2 = new ctkCollapsibleGroupBox(this); auto lay2 = std::make_unique(groupBoxModel2); groupBoxModel2->setObjectName(QString::fromUtf8("model_2_Box")); groupBoxModel2->setTitle(QString::fromUtf8("Model 2")); groupBoxModel2->setMinimumSize(QSize(0, 0)); groupBoxModel2->setCollapsedHeight(5); groupBoxModel2->setCollapsed(false); groupBoxModel2->setFlat(true); groupBoxModel2->setAlignment(Qt::AlignLeft); m_Controls.advancedSettingsLayout->addWidget(groupBoxModel2, 5, 2, 1, 2); connect(lay2->modelBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnModelChanged(const QString &))); connect( lay2->plannerBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(OnTrainerChanged(const QString &))); m_EnsembleParams.push_back(std::move(lay2)); } for (auto &layout : m_EnsembleParams) { layout->setVisible(visible); } }