diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index baad32e9c2..665c7a1714 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,122 +1,125 @@ /*============================================================================ 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 "mitkBinaryThresholdBaseTool.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLabelSetImage.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThreshold(-100), m_SensibleMaximumThreshold(+100), m_LowerThreshold(1), m_UpperThreshold(1) { } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThreshold || lower > m_SensibleMaximumThreshold || upper < m_SensibleMinimumThreshold || upper > m_SensibleMaximumThreshold) { return; } m_LowerThreshold = lower; m_UpperThreshold = upper; if (nullptr != this->GetPreviewSegmentation()) { UpdatePreview(); } } void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { const auto referenceImage = this->GetReferenceData(); if (nullptr != referenceImage) { m_SensibleMinimumThreshold = std::numeric_limits::max(); m_SensibleMaximumThreshold = std::numeric_limits::lowest(); Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { m_SensibleMinimumThreshold = std::min(m_SensibleMinimumThreshold, static_cast(statistics->GetScalarValueMin())); m_SensibleMaximumThreshold = std::max(m_SensibleMaximumThreshold, static_cast(statistics->GetScalarValueMax())); } if (m_LockedUpperThreshold) { m_LowerThreshold = (m_SensibleMaximumThreshold + m_SensibleMinimumThreshold) / 2.0; m_UpperThreshold = m_SensibleMaximumThreshold; } else { double range = m_SensibleMaximumThreshold - m_SensibleMinimumThreshold; m_LowerThreshold = m_SensibleMinimumThreshold + range / 3.0; m_UpperThreshold = m_SensibleMinimumThreshold + 2 * range / 3.0; } bool isFloatImage = false; if ((referenceImage->GetPixelType().GetPixelType() == itk::IOPixelEnum::SCALAR) && (referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::FLOAT || referenceImage->GetPixelType().GetComponentType() == itk::IOComponentEnum::DOUBLE)) { isFloatImage = true; } IntervalBordersChanged.Send(m_SensibleMinimumThreshold, m_SensibleMaximumThreshold, isFloatImage); ThresholdingValuesChanged.Send(m_LowerThreshold, m_UpperThreshold); } } void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage) { AccessByItk_n(inputAtTimeStep, ITKThresholding, (previewImage, timeStep)); } } template void mitk::BinaryThresholdBaseTool::ITKThresholding(const itk::Image* inputImage, - Image* segmentation, + LabelSetImage *segmentation, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; + const auto activeValue = this->GetActiveLabelValueOfPreview(); + this->SetSelectedLabels({activeValue}); + typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(inputImage); filter->SetLowerThreshold(m_LowerThreshold); filter->SetUpperThreshold(m_UpperThreshold); - filter->SetInsideValue(this->GetUserDefinedActiveLabel()); + filter->SetInsideValue(activeValue); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index cac6e3f6af..ff8940d165 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,77 +1,78 @@ /*============================================================================ 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 mitkBinaryThresholdBaseTool_h #define mitkBinaryThresholdBaseTool_h #include #include #include #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public SegWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; mitkClassMacro(BinaryThresholdBaseTool, SegWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThreshold, ScalarType); itkGetMacro(SensibleMaximumThreshold, ScalarType); void InitiateToolByInput() override; void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) override; template void ITKThresholding(const itk::Image* inputImage, - Image* segmentation, unsigned int timeStep); + LabelSetImage *segmentation, + unsigned int timeStep); private: ScalarType m_SensibleMinimumThreshold; ScalarType m_SensibleMaximumThreshold; ScalarType m_LowerThreshold; ScalarType m_UpperThreshold; /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 96afc90601..6478b66ed6 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,256 +1,259 @@ /*============================================================================ 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 "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Picking.svg"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { - AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), mitk::LabelSetImage::UnlabeledValue, emptyTimeStep)); + const auto activeValue = this->GetActiveLabelValueOfPreview(); + this->SetSelectedLabels({activeValue}); + + AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), activeValue, mitk::LabelSetImage::UnlabeledValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 9b89cc0b21..ca649187e3 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,778 +1,818 @@ /*============================================================================ 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 "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); } } 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); - auto* activeLayer = newPreviewImage->GetActiveLabelSet(); - auto* activeLabel = activeLayer->GetActiveLabel(); - if (m_UseSpecialPreviewColor) + auto* activeLabelSet = newPreviewImage->GetActiveLabelSet(); + if (nullptr == activeLabelSet) + { + newPreviewImage->AddLayer(); + activeLabelSet = newPreviewImage->GetActiveLabelSet(); + } + + auto* activeLabel = activeLabelSet->GetActiveLabel(); + if (nullptr == activeLabel) + { + activeLabel = activeLabelSet->AddLabel("toolresult", previewColor); + activeLabelSet = newPreviewImage->GetActiveLabelSet(); + activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); + } + else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); - activeLayer->UpdateLookupTable(activeLabel->GetValue()); + activeLabelSet->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 = { { this->GetUserDefinedActiveLabel(),this->GetUserDefinedActiveLabel() } }; + LabelMappingType labelMapping = {}; - if (LabelTransferScope::SelectedLabels == this->m_LabelTransferScope) - { - labelMapping.clear(); - for (auto label : this->m_SelectedLabels) - { - labelMapping.push_back({ label, label+offset }); - } - } - else if (LabelTransferScope::AllLabels == this->m_LabelTransferScope) + switch (this->m_LabelTransferScope) { - labelMapping.clear(); - const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue()+offset }); - } + case LabelTransferScope::SelectedLabels: + { + for (auto label : this->m_SelectedLabels) + { + labelMapping.push_back({label, label + offset}); + } + } + break; + case LabelTransferScope::AllLabels: + { + const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + { + labelMapping.push_back({labelIter->second->GetValue(), labelIter->second->GetValue() + 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); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } - catch (...) + catch (mitk::Exception& e) { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - throw; + 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 auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working 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 auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic 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(labelSetImage->GetActiveLayer())->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; this->UpdatePrepare(); const auto timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback 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(); } } 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::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue != sourceLabel && LabelSetImage::UnlabeledValue != 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->GetActiveLabelSet()->AddLabel(clonedLabel); } } } 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(previewImage->GetActiveLayer()); + 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 labelSet = source->GetActiveLabelSet(); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h index 3369d6be4d..a4141de0b1 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h @@ -1,314 +1,321 @@ /*============================================================================ 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); /*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 active label will be transfered from preview to segmentation. + 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 "SelectedLabels". - The selected label IDs corespond to the labels of the preview 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(); 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); 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; }; } // namespace #endif