diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 1ae00d3b1d..e20ac8569b 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,849 +1,850 @@ /*============================================================================ 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 "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &SegWithPreviewTool::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); } + m_IsPreviewGenerated = false; } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &SegWithPreviewTool::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::SegWithPreviewTool::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); } this->ConfirmCleanUp(); } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::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::SegWithPreviewTool::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); if (newPreviewImage->GetNumberOfLayers() == 0) { newPreviewImage->AddLayer(); newPreviewImage->SetActiveLayer(0); } auto* activeLabel = newPreviewImage->GetActiveLabel(); if (nullptr == activeLabel) { activeLabel = newPreviewImage->AddLabel("toolresult", previewColor, newPreviewImage->GetActiveLayer()); newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); newPreviewImage->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); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } LabelMappingType labelMapping = {}; switch (this->m_LabelTransferScope) { case LabelTransferScope::SelectedLabels: { for (auto label : this->m_SelectedLabels) { labelMapping.push_back({label, label + offset}); } } break; case LabelTransferScope::AllLabels: { const auto labelValues = this->GetPreviewSegmentation()->GetLabelValuesByGroup(this->GetPreviewSegmentation()->GetActiveLayer()); for (auto labelValue : labelValues) { labelMapping.push_back({ labelValue, labelValue + offset}); } } break; default: { if (m_SelectedLabels.empty()) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " "implementation of derived tool class."; if (m_SelectedLabels.size() > 1) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but more then one selected label is indicated." "Should be only one. Check implementation of derived tool class."; labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); } break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { 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); auto resultSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), destinationImage, timeStep)->Clone(); auto destLSImage = dynamic_cast(destinationImage); //We need to transfer explictly to a copy of the current working image to ensure that labelMapping is done and things //like merge style, overwrite style and locks are regarded. TransferLabelContentAtTimeStep(sourceSlice, resultSlice, destLSImage->GetConstLabelsByValue(destLSImage->GetLabelValuesByGroup(destLSImage->GetActiveLayer())), timeStep, 0, 0, destLSImage->GetUnlabeledLabelLock(), labelMapping, m_MergeStyle, m_OverwriteStyle); //We use WriteBackSegmentationResult to ensure undo/redo is supported also by derived tools of this class. SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, resultSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } catch (mitk::Exception& e) { Tool::ErrorMessage(e.GetDescription()); mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const TimePointType 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 referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage 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::SegWithPreviewTool::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::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const TimePointType 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 referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::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()->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::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; m_IsPreviewGenerated = false; this->UpdatePrepare(); const TimePointType 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 referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage 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(); - if (previewImage->GetNumberOfLabels(previewImage->GetActiveLayer()) > 0) + if (!previewImage->GetAllLabelValues().empty()) { // check if labels exits for the preview m_IsPreviewGenerated = true; } } } 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::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::ConfirmCleanUp() { // default implementation does nothing // reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UNLABELED_VALUE != sourceLabel && LabelSetImage::UNLABELED_VALUE != targetLabel && !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)->Clone(); clonedLabel->SetValue(targetLabel); target->AddLabel(clonedLabel,target->GetActiveLayer(), false, false); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { 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(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const { const auto previewImage = this->GetPreviewSegmentation(); const auto activeLabel = previewImage->GetActiveLabel(); if (nullptr == activeLabel) mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " "preview has no active label indicated. Check " "implementation of the class."; return activeLabel->GetValue(); } const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; const auto labelValues = source->GetLabelValuesByGroup(source->GetActiveLayer()); for (const auto& labelValue : labelValues) { labelMapping.push_back({ labelValue,labelValue }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } bool mitk::SegWithPreviewTool::ConfirmBeforeDeactivation() { return m_IsPreviewGenerated && m_RequestDeactivationConfirmation; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp index 44b094d5dd..3557881382 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -1,499 +1,501 @@ /*============================================================================ 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 "QmitkTotalSegmentatorToolGUI.h" #include "mitkProcessExecutor.h" #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // 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 TotalSegmentator tool can be very slow."; this->ShowErrorMessage(warning); } m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Controls.sysPythonComboBox->addItem("Select"); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->addItem("Select"); m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); m_Controls.pythonEnvComboBox->setDisabled(true); m_Controls.previewButton->setDisabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); m_Controls.subtaskComboBox->addItems(VALID_TASKS); QString welcomeText; this->SetGPUInfo(); if (m_GpuLoader.GetGPUCount() != 0) { welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; } else { welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; } connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); connect(m_Controls.pythonEnvComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); connect(m_Controls.sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") { m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); } m_Controls.fastBox->setChecked(m_Settings.value("TotalSeg/LastFast").toBool()); const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); if (m_IsInstalled) { m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(storageDir).first; m_Installer.SetVirtualEnvPath(m_PythonPath); this->EnableAll(m_IsInstalled); welcomeText += " TotalSegmentator is already found installed."; } else { welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; } this->WriteStatusMessage(welcomeText); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Controls.clearButton->setIcon(deleteIcon); m_Controls.previewButton->setIcon(arrowIcon); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } void QmitkTotalSegmentatorToolGUI::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 QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", Qt::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) { m_Controls.previewButton->setEnabled(isEnable); m_Controls.subtaskComboBox->setEnabled(isEnable); m_Controls.installButton->setEnabled((!isEnable)); } void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() { bool isInstalled = false; const auto [path, version] = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); if (path.isEmpty()) { this->WriteErrorMessage("ERROR: Couldn't find compatible Python."); return; } // check if python 3.12 and ask for confirmation if (version.startsWith("3.12") && QMessageBox::No == QMessageBox::question( nullptr, "Installing TotalSegmentator", QString("WARNING: This is an unsupported version of Python that may not work. " "We recommend using a supported Python version between 3.9 and 3.11.\n\n" "Continue anyway?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { return; } this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); m_Installer.SetSystemPythonPath(path); isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); if (isInstalled) { m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(m_Installer.GetVirtualEnvPath()).first; this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); } else { this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); } this->EnableAll(isInstalled); } void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } try { m_Controls.previewButton->setEnabled(false); qApp->processEvents(); if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); } bool isFast = m_Controls.fastBox->isChecked(); QString subTask = m_Controls.subtaskComboBox->currentText(); if (subTask != VALID_TASKS[0]) { isFast = true; } tool->SetPythonPath(m_PythonPath.toStdString()); tool->SetGpuId(FetchSelectedGPUFromUI()); tool->SetFast(isFast); tool->SetSubTask(subTask.toStdString()); this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); + m_FirstPreviewComputation = false; tool->UpdatePreview(); m_Controls.previewButton->setEnabled(true); - m_FirstPreviewComputation = false; } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); + m_FirstPreviewComputation = true; return; } catch (...) { std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); + m_FirstPreviewComputation = true; return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. { QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (lastSelectedPyEnv != pythonPathTextItem) { m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); } } m_Settings.setValue("TotalSeg/LastFast", m_Controls.fastBox->isChecked()); } void QmitkTotalSegmentatorToolGUI::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 QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) { QString fullPath = pythonPath; bool isPythonExists = false, isExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator.exe")) && isPythonExists; #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; } isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; #endif return isExists; } void QmitkTotalSegmentatorToolGUI::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.sysPythonComboBox->addItem("(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->addItem("(" + envName + "): " + subIt.filePath()); } } } } std::pair QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) { std::pair pyPath; if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.sysPythonComboBox->insertItem(0, path); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.sysPythonComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else { QString uiPyPath = this->GetPythonPathFromUI(pyEnv); pyPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath); } return pyPath; } void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else if (!this->IsTotalSegmentatorInstalled(this->GetPythonPathFromUI(pyEnv))) { this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); m_Controls.previewButton->setDisabled(true); } else {// Show positive status meeage m_Controls.previewButton->setDisabled(false); QString uiPyPath = this->GetPythonPathFromUI(pyEnv); m_PythonPath = QmitkSetupVirtualEnvUtil::GetExactPythonPath(uiPyPath).first; } } QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) { bool isEnabled = false; if (state == Qt::Checked) { isEnabled = true; m_Controls.previewButton->setDisabled(true); m_PythonPath.clear(); } else { m_PythonPath.clear(); m_Controls.previewButton->setDisabled(true); if (m_IsInstalled) { const QString pythonPath = m_Installer.GetVirtualEnvPath(); auto pathObject = QmitkSetupVirtualEnvUtil::GetExactPythonPath(pythonPath); m_PythonPath = pathObject.first; this->EnableAll(m_IsInstalled); } } m_Controls.pythonEnvComboBox->setEnabled(isEnabled); } void QmitkTotalSegmentatorToolGUI::OnClearInstall() { QDir folderPath(m_Installer.GetVirtualEnvPath()); if (folderPath.removeRecursively()) { m_Controls.installButton->setEnabled(true); m_IsInstalled = false; if (!m_Controls.overrideBox->isChecked()) { m_Controls.previewButton->setEnabled(false); } } else { MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; } } bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) { if (GetSystemPythonPath().isEmpty()) { return false; } QDir folderPath(GetBaseDir()); folderPath.mkdir(venvName); if (!folderPath.cd(venvName)) { return false; // Check if directory creation was successful. } mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&PrintProcessEvent); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("venv"); args.push_back(venvName.toStdString()); #ifdef _WIN32 QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; QString pythonExeFolder = "Scripts"; #else QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; QString pythonExeFolder = "bin"; #endif spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment if (folderPath.cd(pythonExeFolder)) { this->SetPythonPath(folderPath.absolutePath()); this->SetPipPath(folderPath.absolutePath()); this->InstallPytorch(); for (auto &package : PACKAGES) { this->PipInstall(package.toStdString(), &PrintProcessEvent); } std::string pythonCode; // python syntax to check if torch is installed with CUDA. pythonCode.append("import torch;"); pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " "installed WITHOUT CUDA');"); this->ExecutePython(pythonCode, &PrintProcessEvent); return true; } return false; } QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp index b39262f9ad..d69d23064b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetToolGUI.cpp @@ -1,1197 +1,1200 @@ /*============================================================================ 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 #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitknnUNetToolGUI, "") QmitknnUNetToolGUI::QmitknnUNetToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // 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."; this->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(); m_nnUNetThread = new QThread(this); m_Worker = new nnUNetDownloadWorker; m_Worker->moveToThread(m_nnUNetThread); m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } QmitknnUNetToolGUI::~QmitknnUNetToolGUI() { m_nnUNetThread->quit(); m_nnUNetThread->wait(); } void QmitknnUNetToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); m_FirstPreviewComputation = true; } 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.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())); connect(m_Controls.startDownloadButton, SIGNAL(clicked()), this, SLOT(OnDownloadModel())); connect(m_Controls.stopDownloadButton, SIGNAL(clicked()), this, SLOT(OnStopDownload())); // Qthreads qRegisterMetaType(); qRegisterMetaType(); connect(this, &QmitknnUNetToolGUI::Operate, m_Worker, &nnUNetDownloadWorker::DoWork); connect(m_Worker, &nnUNetDownloadWorker::Exit, this, &QmitknnUNetToolGUI::OnDownloadWorkerExit); connect(m_nnUNetThread, &QThread::finished, m_Worker, &QObject::deleteLater); m_Controls.multiModalValueLabel->setStyleSheet("font-weight: bold; color: white"); m_Controls.multiModalValueLabel->setVisible(false); m_Controls.requiredModalitiesLabel->setVisible(false); m_Controls.stopDownloadButton->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); QIcon stopIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/status/dialog-error.svg")); m_Controls.stopDownloadButton->setIcon(stopIcon); 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. this->DisableEverything(); QString lastSelectedPyEnv = m_Settings.value("nnUNet/LastPythonPath").toString(); m_Controls.pythonEnvComboBox->setCurrentText(lastSelectedPyEnv); } void QmitknnUNetToolGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); } void QmitknnUNetToolGUI::ClearAllModalities() { m_Controls.multiModalBox->setChecked(false); this->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.multiModalValueLabel->setVisible(false); m_Controls.multiModalBox->setEnabled(false); this->ClearAllComboBoxes(); this->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(); } } 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 (QmitkSingleNodeSelectionWidget *modality : m_Modalities) { mitk::DataNode::Pointer node = modality->GetSelectedNode(); if (nodeNames.find(node->GetName()) == nodeNames.end()) { modals.push_back(dynamic_cast(node->GetData())); nodeNames.insert(node->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 (!this->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; this->WriteStatusMessage(statusMsg); } else if (QFile(ppJsonFilePossibility2).exists()) { tool->SetPostProcessingJsonDirectory(ppJsonFilePossibility2.toStdString()); const QString statusMsg = "Post Processing JSON file found:" + ppJsonFilePossibility2; this->WriteStatusMessage(statusMsg); } else { std::string errorMsg = "No post processing file was found for the selected ensemble combination. Continuing anyway..."; this->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 = this->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, Qt::SkipEmptyParts).first(); planners << trainerPlanner.split(splitterString, Qt::SkipEmptyParts).last(); } trainers.removeDuplicates(); planners.removeDuplicates(); return std::make_pair(trainers, planners); } std::vector QmitknnUNetToolGUI::FetchSelectedFoldsFromUI(ctkCheckableComboBox *foldBox) { std::vector folds; if (foldBox->noneChecked()) { this->CheckAllInCheckableComboBox(foldBox); } QModelIndexList foldList = foldBox->checkedIndexes(); for (const auto &index : foldList) { QString foldQString = foldBox->itemText(index.row()); if(foldQString != "dummy_element_that_nobody_can_see") { foldQString = foldQString.split("_", Qt::SkipEmptyParts).last(); folds.push_back(foldQString.toStdString()); } else { throw std::runtime_error("Folds are not recognized. Please check if your nnUNet results folder structure is legitimate"); } } return folds; } 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(); this->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(":", Qt::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; } void QmitknnUNetToolGUI::DumpJSONfromPickle(const QString &picklePath) { const QString pickleFile = picklePath + QDir::separator() + m_PICKLE_FILENAME; const QString jsonFile = picklePath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; if (!QFile::exists(jsonFile)) { mitk::ProcessExecutor::Pointer spExec = mitk::ProcessExecutor::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(pickleFile.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(jsonFile.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(); this->WriteStatusMessage( "Parsing failed in backend. Multiple Modalities will now have to be manually entered by the user."); } } } void QmitknnUNetToolGUI::ExportAvailableModelsAsJSON(const QString &resultsFolder) { const QString jsonPath = resultsFolder + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (!QFile::exists(jsonPath)) { auto spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back("--export"); args.push_back(resultsFolder.toStdString()); try { spExec->Execute(m_PythonPath.toStdString(), "nnUNet_print_available_pretrained_models", args); } catch (const mitk::Exception &e) { MITK_ERROR << "Exporting information FAILED." << e.GetDescription(); this->WriteStatusMessage("Exporting information FAILED."); } } } void QmitknnUNetToolGUI::DisplayMultiModalInfoFromJSON(const QString &jsonPath) { std::ifstream file(jsonPath.toStdString()); if (file.is_open()) { auto jsonObj = nlohmann::json::parse(file, nullptr, false); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse \"" << jsonPath.toStdString() << "\" as JSON object!"; return; } auto num_mods = jsonObj["num_modalities"].get(); this->ClearAllModalLabels(); if (num_mods > 1) { m_Controls.multiModalBox->setChecked(true); m_Controls.multiModalBox->setEnabled(false); m_Controls.multiModalValueLabel->setText(QString::number(num_mods)); OnModalitiesNumberChanged(num_mods); m_Controls.advancedSettingsLayout->update(); auto obj = jsonObj["modalities"]; int count = 0; for (const auto &value : obj) { QLabel *label = new QLabel(QString::fromStdString("" + value.get() + ""), this); m_ModalLabels.push_back(label); m_Controls.advancedSettingsLayout->addWidget(label, m_UI_ROWS + 1 + count, 0); count++; } m_Controls.advancedSettingsLayout->update(); } else { m_Controls.multiModalBox->setChecked(false); } } } void QmitknnUNetToolGUI::FillAvailableModelsInfoFromJSON(const QString &jsonPath) { std::ifstream file(jsonPath.toStdString()); if (file.is_open() && m_Controls.availableBox->count() < 1) { auto jsonObj = nlohmann::json::parse(file, nullptr, false); if (jsonObj.is_discarded() || !jsonObj.is_object()) { MITK_ERROR << "Could not parse \"" << jsonPath.toStdString() << "\" as JSON object!"; return; } for (const auto &obj : jsonObj.items()) { m_Controls.availableBox->addItem(QString::fromStdString(obj.key())); } } } 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::SetComboBoxToNone(ctkCheckableComboBox* comboBox) { comboBox->clear(); comboBox->addItem("dummy_element_that_nobody_can_see"); qobject_cast(comboBox->view())->setRowHidden(0, true); // For the cosmetic purpose of showing "None" on the combobox. } /* ---------------------SLOTS---------------------------------------*/ 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)) { this->ProcessEnsembleModelsParams(tool); } else { this->ProcessModelParams(tool); } pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); QString pythonPath = m_PythonPath; if (!this->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()) { tool->m_OtherModalPaths.clear(); tool->m_OtherModalPaths = FetchMultiModalImagesFromUI(); tool->MultiModalOn(); } 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; this->WriteStatusMessage( QString("STATUS: Starting Segmentation task... This might take a while.")); + m_FirstPreviewComputation = false; tool->UpdatePreview(); if (nullptr == tool->GetOutputBuffer()) { this->SegmentationProcessFailed(); } else { this->SegmentationResultHandler(tool); if (doCache) { this->AddToCache(hashKey, tool->GetOutputBuffer()); } tool->ClearOutputBuffer(); } 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())); this->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(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); + m_FirstPreviewComputation = true; return; } catch (...) { std::string errorMsg = "Unkown error occured while generation nnUNet segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); tool->PredictOff(); + m_FirstPreviewComputation = true; return; } if (!pythonPathTextItem.isEmpty()) { // only cache if the prediction ended without errors. m_Settings.setValue("nnUNet/LastPythonPath", pythonPathTextItem); } } } void QmitknnUNetToolGUI::OnRefreshPresssed() { const QString resultsFolder = m_Controls.modeldirectoryBox->directory(); this->OnDirectoryChanged(resultsFolder); } void QmitknnUNetToolGUI::OnDirectoryChanged(const QString &resultsFolder) { m_IsResultsFolderValid = false; m_Controls.previewButton->setEnabled(false); this->ClearAllComboBoxes(); this->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; } this->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); m_Controls.previewButton->setEnabled(false); this->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); }); } } 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); this->ShowEnsembleLayout(false); m_Controls.trainerBox->clear(); m_Controls.plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); if(trainerPlanners.isEmpty()) { this->ShowErrorMessage("No plans.pkl found for "+model.toStdString()+". Check your directory or download the task again."); this->SetComboBoxToNone(m_Controls.foldBox); return; } QStringList trainers, planners; std::tie(trainers, planners) = ExtractTrainerPlannerFromString(trainerPlanners); 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()) { m_Controls.previewButton->setEnabled(false); for (auto &layout : m_EnsembleParams) { if (box == layout->modelBox) { layout->trainerBox->clear(); layout->plannerBox->clear(); auto trainerPlanners = m_ParentFolder->getTrainerPlannersForTask(selectedTask, model); if(trainerPlanners.isEmpty()) { this->ShowErrorMessage("No plans.pkl found for "+model.toStdString()+". Check your directory or download the task again."); this->SetComboBoxToNone(layout->foldBox); return; } 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; } m_IsResultsFolderValid = false; QString parentPath; 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); if(folds.isEmpty()) { this->ShowErrorMessage("No valid folds found. Check your directory or download the task again."); this->SetComboBoxToNone(m_Controls.foldBox); return; } 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) { m_IsResultsFolderValid = true; this->CheckAllInCheckableComboBox(m_Controls.foldBox); auto tempPath = QStringList() << m_ParentFolder->getResultsFolder() << "nnUNet" << selectedModel << selectedTask << QString("%1__%2").arg(selectedTrainer, plannerSelected); parentPath = QDir::cleanPath(tempPath.join(QDir::separator())); } } 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); if(folds.isEmpty()) { this->ShowErrorMessage("No valid folds found. Check your directory."); this->SetComboBoxToNone(layout->foldBox); return; } 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) { this->CheckAllInCheckableComboBox(layout->foldBox); m_IsResultsFolderValid = true; auto tempPath = QStringList() << m_ParentFolder->getResultsFolder() << "nnUNet" << selectedModel << selectedTask << QString("%1__%2").arg(selectedTrainer, plannerSelected); parentPath = QDir::cleanPath(tempPath.join(QDir::separator())); } break; } } } if (m_IsResultsFolderValid) { m_Controls.previewButton->setEnabled(true); const QString mitkJsonFile = parentPath + QDir::separator() + m_MITK_EXPORT_JSON_FILENAME; this->DumpJSONfromPickle(parentPath); if (QFile::exists(mitkJsonFile)) { this->DisplayMultiModalInfoFromJSON(mitkJsonFile); } } } void QmitknnUNetToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); } } else if (!this->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"; this->ShowErrorMessage(warning); this->DisableEverything(); m_Controls.availableBox->clear(); } else { m_Controls.modeldirectoryBox->setEnabled(true); m_Controls.refreshdirectoryBox->setEnabled(true); m_Controls.multiModalBox->setEnabled(true); QString setVal = this->FetchResultsFolderFromEnv(); if (!setVal.isEmpty()) { m_Controls.modeldirectoryBox->setDirectory(setVal); } this->OnRefreshPresssed(); m_PythonPath = pyEnv.mid(pyEnv.indexOf(" ") + 1); #ifdef _WIN32 if (!(m_PythonPath.endsWith("Scripts", Qt::CaseInsensitive) || m_PythonPath.endsWith("Scripts/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("Scripts"); } #else if (!(m_PythonPath.endsWith("bin", Qt::CaseInsensitive) || m_PythonPath.endsWith("bin/", Qt::CaseInsensitive))) { m_PythonPath += QDir::separator() + QString("bin"); } #endif // Export available model info as json and fill them for Download QString tempPath = QString::fromStdString(mitk::IOUtil::GetTempPath()); this->ExportAvailableModelsAsJSON(tempPath); const QString jsonPath = tempPath + QDir::separator() + m_AVAILABLE_MODELS_JSON_FILENAME; if (QFile::exists(jsonPath)) { this->FillAvailableModelsInfoFromJSON(jsonPath); } } } 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.requiredModalitiesLabel->setVisible(visibility); m_Controls.multiModalValueLabel->setVisible(visibility); if (!visibility) { this->OnModalitiesNumberChanged(0); m_Controls.multiModalValueLabel->setText("0"); this->ClearAllModalLabels(); } } } } void QmitknnUNetToolGUI::OnModalitiesNumberChanged(int num) { while (num > static_cast(m_Modalities.size())) { QmitkSingleNodeSelectionWidget *multiModalBox = new QmitkSingleNodeSelectionWidget(this); mitk::nnUNetTool::Pointer tool = this->GetConnectedToolAs(); multiModalBox->SetDataStorage(tool->GetDataStorage()); multiModalBox->SetInvalidInfo("Select corresponding modalities"); multiModalBox->SetNodePredicate(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()) && !m_Modalities.empty()) { QmitkSingleNodeSelectionWidget *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); } void QmitknnUNetToolGUI::SegmentationProcessFailed() { + m_FirstPreviewComputation = true; this->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."; this->ShowErrorMessage(stream.str()); } void QmitknnUNetToolGUI::SegmentationResultHandler(mitk::nnUNetTool *tool, bool forceRender) { if (forceRender) { tool->RenderOutputBuffer(); } - m_FirstPreviewComputation = false; this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); this->ActualizePreviewLabelVisibility(); } 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); } } void QmitknnUNetToolGUI::OnDownloadModel() { auto selectedTask = m_Controls.availableBox->currentText(); if(!selectedTask.isEmpty()) { auto spExec = mitk::ProcessExecutor::New(); mitk::ProcessExecutor::ArgumentListType args; args.push_back(selectedTask.toStdString()); this->WriteStatusMessage( "Downloading the requested task in to the selected Results Folder. This might take some time " "depending on your internet connection..."); m_Processes["DOWNLOAD"] = spExec; if (!m_nnUNetThread->isRunning()) { MITK_DEBUG << "Starting thread..."; m_nnUNetThread->start(); } QString resultsFolder = m_ParentFolder->getResultsFolder(); emit Operate(resultsFolder, m_PythonPath, spExec, args); m_Controls.stopDownloadButton->setVisible(true); m_Controls.startDownloadButton->setVisible(false); } } void QmitknnUNetToolGUI::OnDownloadWorkerExit(const bool isSuccess, const QString message) { if (isSuccess) { this->WriteStatusMessage(message + QString(" Click Refresh Results Folder to use the new Task.")); } else { MITK_ERROR << "Download FAILED! " << message.toStdString(); this->WriteStatusMessage(QString("Download failed. Check your internet connection. " + message)); } m_Controls.stopDownloadButton->setVisible(false); m_Controls.startDownloadButton->setVisible(true); } void QmitknnUNetToolGUI::OnStopDownload() { mitk::ProcessExecutor::Pointer spExec = m_Processes["DOWNLOAD"]; spExec->KillProcess(); this->WriteStatusMessage("Download Killed by the user."); m_Controls.stopDownloadButton->setVisible(false); m_Controls.startDownloadButton->setVisible(true); } void QmitknnUNetToolGUI::OnClearCachePressed() { m_Cache.clear(); this->UpdateCacheCountOnUI(); }