diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 3e7aa8d51c..1ae00d3b1d 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,840 +1,849 @@ /*============================================================================ 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); } } 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) + { // 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/Segmentation/Interactions/mitkSegWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h index 109fb9370e..4aa461a30e 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h @@ -1,326 +1,339 @@ /*============================================================================ 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 mitkSegWithPreviewTool_h #define mitkSegWithPreviewTool_h #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 SegWithPreviewTool : public Tool { public: mitkClassMacro(SegWithPreviewTool, 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); itkSetMacro(UseSpecialPreviewColor, bool); itkGetMacro(UseSpecialPreviewColor, bool); itkBooleanMacro(UseSpecialPreviewColor); + itkSetMacro(RequestDeactivationConfirmation, bool); + itkGetMacro(RequestDeactivationConfirmation, bool); + itkBooleanMacro(RequestDeactivationConfirmation); + /*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 LabelTransferScope { ActiveLabel, //Only the selected label will be transfered from the preview segmentation //to the result segmentation. //If this mode is selected the class expects that GetSelectedLabels indicate //the label in the preview. 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 SetLabelTransferScope(LabelTransferScope labelTransferScope); itkGetMacro(LabelTransferScope, LabelTransferScope); 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 LabelTransferScope is set to "ActiveLabel" or "SelectedLabels". @remark If the LabelTransferScope=="ActiveLabel", the class expects only one label to be selected. @remark The selected label IDs corespond to the labels of the preview image.*/ void SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer); itkGetMacro(SelectedLabels, SelectedLabelVectorType); enum class LabelTransferMode { MapLabel, //Only the active label will be transfered from preview to segmentation. AddLabel //The labels defined as selected labels will be transfered. }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferMode(LabelTransferMode labelTransferMode); itkGetMacro(LabelTransferMode, LabelTransferMode); 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; LabelSetImage* GetTargetSegmentation() 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; SegWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~SegWithPreviewTool() 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, TimeStepType timestep); /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::Pointer GetImageByTimeStep(Image* image, TimeStepType 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 after the Confirmation of the preview is performed. It is called by ConfirmSegmentation. Default implementation does nothing.*/ virtual void ConfirmCleanUp(); using LabelMappingType = std::vector >; /** 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. @param labelMapping the mapping that should be used for transfering the labels. */ virtual void PreparePreviewToResultTransfer(const LabelMappingType& labelMapping); static void TransferLabelInformation(const LabelMappingType& 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; LabelSetImage::LabelValueType GetActiveLabelValueOfPreview() const; itkGetConstMacro(UserDefinedActiveLabel, Label::PixelType); itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); + bool ConfirmBeforeDeactivation() override; + private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping); 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(); /**Returns that label mapping between preview segmentation (first element of pair) and result segmentation (second element of pair). The content depends on the settings of LabelTransferMode and LabelTransferScope*/ LabelMappingType 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; /** Controls if for the preview of the active label a special preview color is used. * If set to false, coloring will stay in the preview like it is in the working image.*/ bool m_UseSpecialPreviewColor = true; 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; LabelTransferScope m_LabelTransferScope = LabelTransferScope::ActiveLabel; SelectedLabelVectorType m_SelectedLabels = {}; LabelTransferMode m_LabelTransferMode = LabelTransferMode::MapLabel; + + bool m_IsPreviewGenerated = false; + + /** This variable tracks if there should be a user-confirmation before a tool is deactivated or not. + * Call RequestDeactivationConfirmationOn() in the tool class to avail this feature. + */ + bool m_RequestDeactivationConfirmation = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 81ce0c816a..101156fbab 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,339 +1,344 @@ /*============================================================================ 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 "mitkTool.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include #include // us #include #include // itk #include namespace mitk { itkEventMacroDefinition(ToolEvent, itk::ModifiedEvent); } mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig(""), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractionConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } mitk::ToolManager* mitk::Tool::GetToolManager() const { return m_ToolManager; } mitk::DataStorage* mitk::Tool::GetDataStorage() const { if (nullptr != m_ToolManager) { return m_ToolManager->GetDataStorage(); } return nullptr; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractionConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayActionEventBroadcast = dynamic_cast( us::GetModuleContext()->GetService(eventObserver)); if (nullptr != displayActionEventBroadcast) { // remember the original configuration m_DisplayInteractionConfigs.insert(std::make_pair(eventObserver, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->AddEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractionConfig : m_DisplayInteractionConfigs) { if (displayInteractionConfig.first) { auto displayActionEventBroadcast = static_cast( us::GetModuleContext()->GetService(displayInteractionConfig.first)); if (nullptr != displayActionEventBroadcast) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(displayInteractionConfig.second); } } } m_DisplayInteractionConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); segmentation->SetActiveLayer(0); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->AddLabel(label,segmentation->GetActiveLayer()); segmentation->SetActiveLabel(label->GetValue()); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } + +bool mitk::Tool::ConfirmBeforeDeactivation() +{ + return false; +} diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index 662c9b64c8..e664d96287 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,273 +1,280 @@ /*============================================================================ 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 mitkTool_h #define mitkTool_h #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::EventStateMachine, which can follow any transition pattern that it likes. Every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the interaction configuration in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the configuration. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a state machine name (interactor type). Names and .xml-files for valid state machines can be found in different "Interaction" directories (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ //[[deprecated]] DEPRECATED(virtual const char **GetXPM() const) = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const; DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const; /** Function used to check if a tool can handle the referenceData and (if specified) the working data. @pre referenceData must be a valid pointer @param referenceData Pointer to the data that should be checked as valid reference for the tool. @param workingData Pointer to the data that should be checked as valid working data for this tool. This parameter can be null if no working data is specified so far.*/ virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; + /** + * @brief Method call to invoke a dialog box just before exiting. + * The method can be reimplemented in the respective tool class with business logic + * on when there should be a confirmation dialog from the user before the tool exits. + */ + virtual bool ConfirmBeforeDeactivation(); + protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); /** Returns the pointer to the tool manager of the tool. May be null.*/ ToolManager* GetToolManager() const; /** Returns the data storage provided by the toolmanager. May be null (e.g. if ToolManager is not set).*/ mitk::DataStorage* GetDataStorage() const; void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; private: ToolManager* m_ToolManager; // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractionConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index edab65b958..9d93d41ccf 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,348 +1,349 @@ /*============================================================================ 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 "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); + this->RequestDeactivationConfirmationOn(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::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; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); m_ProgressCommand->SetProgress(5); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, 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("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; m_ProgressCommand->SetProgress(20); IOUtil::Save(inputAtTimeStep, inputImagePath); m_ProgressCommand->SetProgress(50); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { outputImagePath = outDir; this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); Image::Pointer outputImage = IOUtil::Load(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } m_ProgressCommand->SetProgress(180); mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); preview->RemoveLabels(preview->GetAllLabelValues()); if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); const auto layerIndex = aggloLabelImage->AddLayer(); aggloLabelImage->SetActiveLayer(layerIndex); for (auto const &outputImagePath : filePaths) { double rgba[4]; aggloLabelImage->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); aggloLabelImage->AddLabel(label, layerIndex, false, false); Image::Pointer outputImage = IOUtil::Load(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); mitk::TransferLabelContent(source, aggloLabelImage, aggloLabelImage->GetConstLabelsByValue(aggloLabelImage->GetLabelValuesByGroup(layerIndex)), 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; #ifdef _WIN32 command += ".exe"; #endif args.clear(); args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { std::regex sanitizer(R"([^A-Za-z0-9_])"); std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { if (line > 111 && line < 229) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { std::string sanitized = std::regex_replace(val, sanitizer, ""); m_LabelMapTotal[std::stoi(key)] = sanitized; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map &labelMap) { auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); dest->AddLabel(label, 0,false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; std::filesystem::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 995433d58e..d5c3822f80 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,623 +1,637 @@ /*============================================================================ 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. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, &QButtonGroup::idClicked, this, &QmitkToolSelectionBox::toolButtonClicked); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); + mitk::Tool *tool = m_ToolManager->GetActiveTool(); + if (tool && tool->ConfirmBeforeDeactivation() && + QMessageBox::No == QMessageBox::question(nullptr, + tool->GetName(), + QStringLiteral("The %1 tool currently has unconfirmed results. " + "Do you really want to discard the results by " + "exiting the tool now?").arg(tool->GetName()), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No)) + { + toolButton->setChecked(false); + return; + } + if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { layout->addWidget(gui); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); if (selectedToolButton) { selectedToolButton->setChecked(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; this->UpdateButtonsEnabledState(); } void QmitkToolSelectionBox::setEnabled(bool enable) { if (QWidget::isEnabled() == enable) return; QWidget::setEnabled(enable); if (enable) { m_ToolManager->RegisterClient(); auto id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } void QmitkToolSelectionBox::UpdateButtonsEnabledState() { auto buttons = m_ToolButtonGroup->buttons(); const auto refDataNode = m_ToolManager->GetReferenceData(0); const mitk::BaseData* refData = nullptr; if (nullptr != refDataNode) { refData = refDataNode->GetData(); } const auto workingDataNode = m_ToolManager->GetWorkingData(0); const mitk::BaseData* workingData = nullptr; if (nullptr != workingDataNode) { workingData = workingDataNode->GetData(); } for (const auto& button : std::as_const(buttons)) { const auto buttonID = m_ToolButtonGroup->id(button); const auto toolID = m_ToolIDForButtonID[buttonID]; const auto tool = m_ToolManager->GetToolById(toolID); button->setEnabled(tool->CanHandle(refData, workingData)); } } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); delete btn; } mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { button->setText(label); // a label button->setToolTip(tooltip); QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); this->UpdateButtonsEnabledState(); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok | QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok | QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegularExpression("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp index 68dfb6aa6b..be53422f7e 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp @@ -1,55 +1,55 @@ /*============================================================================ 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.s ============================================================================*/ #include "QmitknnUNetGPU.h" #include QmitkGPULoader::QmitkGPULoader() { QProcess process; - process.start("nvidia-smi --query-gpu=name,memory.total --format=csv"); + process.start("nvidia-smi", QStringList() << "--query-gpu=name,memory.total" << "--format=csv"); process.waitForFinished(-1); QStringList infoStringList; while (process.canReadLine()) { QString line = process.readLine(); if (!line.startsWith("name")) { infoStringList << line; } } unsigned int count = 0; foreach (QString infoString, infoStringList) { QmitkGPUSpec spec; QStringList gpuDetails; gpuDetails = infoString.split(","); if(gpuDetails.count() == 2) { spec.name = gpuDetails.at(0).trimmed(); spec.id = count; spec.memory = gpuDetails.at(1).trimmed(); this->m_Gpus.push_back(spec); ++count; } } } int QmitkGPULoader::GetGPUCount() const { return static_cast(m_Gpus.size()); } std::vector QmitkGPULoader::GetAllGPUSpecs() { return m_Gpus; }