diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index 831816b898..647fa8b80d 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,101 +1,130 @@ /*============================================================================ 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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } +void mitk::AutoSegmentationTool::Activated() +{ + Superclass::Activated(); + + m_NoneOverwriteTargetSegmentationNode = nullptr; +} + +void mitk::AutoSegmentationTool::Deactivated() +{ + m_NoneOverwriteTargetSegmentationNode = nullptr; + + Superclass::Deactivated(); +} + const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { - m_OverwriteExistingSegmentation = overwrite; + if (m_OverwriteExistingSegmentation != overwrite) + { + m_OverwriteExistingSegmentation = overwrite; + m_NoneOverwriteTargetSegmentationNode = nullptr; + } } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } -mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() +mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() const { mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); if (!m_OverwriteExistingSegmentation) { - mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); - if (refNode.IsNull()) + if (m_NoneOverwriteTargetSegmentationNode.IsNull()) { - // TODO create and use segmentation exceptions instead!! - MITK_ERROR << "No valid reference data!"; - return nullptr; + mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); + if (refNode.IsNull()) + { + // TODO create and use segmentation exceptions instead!! + MITK_ERROR << "No valid reference data!"; + return nullptr; + } + + std::string nodename = refNode->GetName() + "_" + this->GetName(); + mitk::Color color; + color.SetRed(1); + color.SetBlue(0); + color.SetGreen(0); + //create a new segmentation node based on the current segmentation as template + m_NoneOverwriteTargetSegmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); } - - std::string nodename = refNode->GetName() + "_" + this->GetName(); - mitk::Color color; - color.SetRed(1); - color.SetBlue(0); - color.SetGreen(0); - //create a new segmentation node based on the current segmentation as template - segmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); - - m_ToolManager->GetDataStorage()->Add(segmentationNode, refNode); + segmentationNode = m_NoneOverwriteTargetSegmentationNode; } return segmentationNode; } + +void mitk::AutoSegmentationTool::EnsureTargetSegmentationNodeInDataStorage() const +{ + auto targetNode = this->GetTargetSegmentationNode(); + if (!m_ToolManager->GetDataStorage()->Exists(targetNode)) + { + m_ToolManager->GetDataStorage()->Add(targetNode, m_ToolManager->GetReferenceData(0)); + } +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index bb717c23cd..69624986c8 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,72 +1,84 @@ /*============================================================================ 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 mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); + void Activated() override; + void Deactivated() override; + /** This function controls wether a confirmed segmentation should replace the old * segmentation/working node (true) or if it should be stored as new and additional * node (false). */ void SetOverwriteExistingSegmentation(bool overwrite); /** * @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 Depending on the selected mode either returns the currently selected segmentation - * or creates a new one from the selected reference data and adds the new segmentation - * to the datastorage + * @brief Depending on the selected mode either returns the currently selected segmentation node + * or (if overwrite mode is false) creates a new one from the selected reference data. + * @remark Please keep in mind that new created nodes are not automatically added to the data storage. + * Derived tools can call EnsureTargetSegmentationNodeInDataStorage to ensure it as soon as it is clear + * that the target segmentation node will be/is confirmed. * @return a mitk::DataNode which contains a segmentation image */ - virtual mitk::DataNode *GetTargetSegmentationNode(); + virtual DataNode *GetTargetSegmentationNode() const; protected: AutoSegmentationTool(); // purposely hidden AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); + void EnsureTargetSegmentationNodeInDataStorage() const; + bool m_OverwriteExistingSegmentation; + + private: + /**Contains the node returned by GetTargetSementationNode if m_OverwriteExistingSegmentation == false. Then + * GetTargetSegmentation generates a new target segmentation node.*/ + mutable DataNode::Pointer m_NoneOverwriteTargetSegmentationNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp index ebb78ea6b9..ca10d01e4c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -1,519 +1,523 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : AutoSegmentationTool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() { } bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } void mitk::AutoSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_ToolManager->RoiDataChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); m_ToolManager->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = 0; 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 { m_ToolManager->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::Deactivated() { m_ToolManager->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); m_ToolManager->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = m_ToolManager->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() { if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) { // 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) { m_ToolManager->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() { itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { mitk::LabelSetImage::ConstPointer workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... newPreviewImage->GetActiveLabel()->SetColor(previewColor); newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); } else { mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { auto newPreviewImage = workingImageBin->Clone(); if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); } 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 = m_ToolManager->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } template static void ITKSetVolume(const itk::Image *originalImage, mitk::Image *segmentation, unsigned int timeStep) { auto constPixelContainer = originalImage->GetPixelContainer(); //have to make a const cast because itk::PixelContainer does not provide a const correct access :( auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); } void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { - Image::ConstPointer image3D = this->GetImageByTimeStep(sourceImage, timeStep); + Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); - if (image3D->GetPixelType() != destinationImage->GetPixelType()) + 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_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()) - { //only transfer a specific slice defined by the working plane - + { + auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); + SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume - if (image3D->GetDimension() == 2) + if (sourceImageAtTimeStep->GetDimension() == 2) { AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, destinationImage, timeStep); + sourceImageAtTimeStep, ITKSetVolume, 2, destinationImage, timeStep); } else { AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, destinationImage, timeStep); + sourceImageAtTimeStep, ITKSetVolume, 3, destinationImage, timeStep); } } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { - TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { mitk::PadImageFilter::Pointer padFilter = mitk::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()); } m_ToolManager->SetWorkingData(resultSegmentationNode); m_ToolManager->GetWorkingData(0)->Modified(); + this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() { mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); if (node.IsNotNull()) { mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = mitk::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 < inputImage->GetTimeSteps(); ++timeStep) + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; + auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); + auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback image. - feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, timeStep); + feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); } else { //work on the whole feedback image - feedBackImage = this->GetImageByTimeStep(inputImage, timeStep); + feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); } this->DoUpdatePreview(feedBackImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp index 44cabc685a..923d20a68e 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp @@ -1,318 +1,335 @@ /*============================================================================ 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 "mitkFastMarchingBaseTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" #include "mitkInteractionPositionEvent.h" #include "mitkImageAccessByItk.h" +#include "mitkSegTool2D.h" + +#include + // itk filter #include "itkBinaryThresholdImageFilter.h" #include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" #include "itkSigmoidImageFilter.h" // us #include #include #include #include mitk::FastMarchingBaseTool::FastMarchingBaseTool() - : AutoSegmentationWithPreviewTool("FastMarchingTool"), + : AutoSegmentationWithPreviewTool(false, "FastMarchingTool"), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0) { } mitk::FastMarchingBaseTool::~FastMarchingBaseTool() { } bool mitk::FastMarchingBaseTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if(!Superclass::CanHandle(referenceData, workingData)) return false; if (referenceData == nullptr) return false; auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingBaseTool::GetXPM() const { return nullptr; // mitkFastMarchingBaseTool_xpm; } us::ModuleResource mitk::FastMarchingBaseTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } us::ModuleResource mitk::FastMarchingBaseTool::GetCursorIconResource() const { us::Module* module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_Cursor_32x32.png"); return resource; } void mitk::FastMarchingBaseTool::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; } } void mitk::FastMarchingBaseTool::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; } } } void mitk::FastMarchingBaseTool::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; } } void mitk::FastMarchingBaseTool::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; } } void mitk::FastMarchingBaseTool::Activated() { Superclass::Activated(); m_SeedsAsPointSet = mitk::PointSet::New(); + //ensure that the seed points are visible for all timepoints. + dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); + m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); } void mitk::FastMarchingBaseTool::Deactivated() { this->ClearSeeds(); m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } void mitk::FastMarchingBaseTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::FastMarchingBaseTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { auto newWorkingPlaneGeometry = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone(); // if click was on another plane and we are in 2D mode wie should reset the seeds if (TOOL_DIMENSION == 2 && ( nullptr == this->GetWorkingPlaneGeometry() || !this->GetWorkingPlaneGeometry()->IsOnPlane(newWorkingPlaneGeometry.GetPointer()))) { this->ClearSeeds(); this->SetWorkingPlaneGeometry(newWorkingPlaneGeometry); } m_SeedsAsPointSet->InsertPoint(m_SeedsAsPointSet->GetSize(), positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::OnDelete(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { // delete last seed point if (this->m_SeedsAsPointSet->GetSize() > 0) { // delete last point in pointset - somehow ugly m_SeedsAsPointSet->GetPointSet()->GetPoints()->DeleteIndex(m_SeedsAsPointSet->GetSize() - 1); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::ClearSeeds() { if (this->m_SeedsAsPointSet.IsNotNull()) { // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); + //ensure that the seed points are visible for all timepoints. + dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); } } template void mitk::FastMarchingBaseTool::DoITKFastMarching(const itk::Image* inputImage, mitk::Image* previewImage, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry) { // typedefs for itk pipeline typedef itk::Image InputImageType; typedef float InternalPixelType; - typedef itk::Image InternalImageType; + typedef itk::Image InternalImageType; typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; typedef itk::Image OutputImageType; typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; typedef itk::SigmoidImageFilter SigmoidFilterType; typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; typedef itk::FastMarchingImageFilter FastMarchingFilterType; typedef FastMarchingFilterType::NodeContainer NodeContainer; typedef FastMarchingFilterType::NodeType NodeType; //convert point set seed into trialpoint NodeContainer::Pointer trialPoints = NodeContainer::New(); trialPoints->Initialize(); for (auto pos = m_SeedsAsPointSet->Begin(); pos != m_SeedsAsPointSet->End(); ++pos) { mitk::Point3D clickInIndex; inputGeometry->WorldToIndex(pos->Value(), clickInIndex); itk::Index seedPosition; for (unsigned int dim = 0; dim < VImageDimension; ++dim) { seedPosition[dim] = clickInIndex[dim]; } NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); trialPoints->InsertElement(trialPoints->Size(), node); } // assemble pipeline auto smoothFilter = SmoothingFilterType::New(); smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); smoothFilter->SetTimeStep(0.05); smoothFilter->SetNumberOfIterations(2); smoothFilter->SetConductanceParameter(9.0); auto gradientMagnitudeFilter = GradientFilterType::New(); gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); gradientMagnitudeFilter->SetSigma(m_Sigma); auto sigmoidFilter = SigmoidFilterType::New(); sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); sigmoidFilter->SetAlpha(m_Alpha); sigmoidFilter->SetBeta(m_Beta); sigmoidFilter->SetOutputMinimum(0.0); sigmoidFilter->SetOutputMaximum(1.0); auto fastMarchingFilter = FastMarchingFilterType::New(); fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); fastMarchingFilter->SetStoppingValue(m_StoppingValue); fastMarchingFilter->SetTrialPoints(trialPoints); auto thresholdFilter = ThresholdingFilterType::New(); thresholdFilter->SetLowerThreshold(m_LowerThreshold); thresholdFilter->SetUpperThreshold(m_UpperThreshold); thresholdFilter->SetOutsideValue(0); thresholdFilter->SetInsideValue(1.0); // set up pipeline smoothFilter->SetInput(inputImage); gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); thresholdFilter->Update(); if (nullptr == this->GetWorkingPlaneGeometry()) { previewImage->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } else { - //TODO -> rework SegTool2D to make it usable here - //this->WriteBackSegmentationResult(m_WorkingPlane, segmentationResult, workingImageTimeStep); + mitk::Image::Pointer sliceImage = mitk::Image::New(); + mitk::CastToMitkImage(thresholdFilter->GetOutput(), sliceImage); + SegTool2D::WriteSliceToVolume(previewImage, this->GetWorkingPlaneGeometry(), sliceImage, timeStep, false); } } void mitk::FastMarchingBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedsAsPointSet.IsNotNull() && m_SeedsAsPointSet->GetSize()>0) { - AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); + if (nullptr == this->GetWorkingPlaneGeometry()) + { + AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); + } + else + { + AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 2, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); + } } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h index 05f2542f9d..eea9bb1d47 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h @@ -1,113 +1,114 @@ /*============================================================================ 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 mitkFastMarchingBaseTool_h_Included #define mitkFastMarchingBaseTool_h_Included #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" #include "itkImage.h" #include "itkFastMarchingImageFilter.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool base class. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ class MITKSEGMENTATION_EXPORT FastMarchingBaseTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(FastMarchingBaseTool, AutoSegmentationWithPreviewTool); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); /// \brief Clear all seed points. void ClearSeeds(); - const unsigned int TOOL_DIMENSION = 3; protected: FastMarchingBaseTool(); ~FastMarchingBaseTool() override; + unsigned int TOOL_DIMENSION = 3; + void ConnectActionsAndFunctions() override; /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Delete action of StateMachine pattern virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; template void DoITKFastMarching(const itk::Image* inputImage, mitk::Image* segmentation, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points mitk::PointSet::Pointer m_SeedsAsPointSet; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp index b4220d6acb..472026f6b7 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp @@ -1,483 +1,35 @@ /*============================================================================ 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 "mitkFastMarchingTool.h" -#include "mitkToolManager.h" -#include "mitkBaseRenderer.h" -#include "mitkInteractionConst.h" -#include "mitkRenderingManager.h" - -#include "itkOrImageFilter.h" -#include "mitkImageTimeSelector.h" - -// us -#include -#include -#include -#include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool, "FastMarching2D tool"); } mitk::FastMarchingTool::FastMarchingTool() - : FeedbackContourTool("FastMarchingTool"), - m_NeedUpdate(true), - m_CurrentTimeStep(0), - m_PositionEvent(nullptr), - m_LowerThreshold(0), - m_UpperThreshold(200), - m_StoppingValue(100), - m_Sigma(1.0), - m_Alpha(-0.5), - m_Beta(3.0) + : FastMarchingBaseTool() { + TOOL_DIMENSION = 2; } mitk::FastMarchingTool::~FastMarchingTool() { - if (this->m_SmoothFilter.IsNotNull()) - this->m_SmoothFilter->RemoveAllObservers(); - - if (this->m_SigmoidFilter.IsNotNull()) - this->m_SigmoidFilter->RemoveAllObservers(); - - if (this->m_GradientMagnitudeFilter.IsNotNull()) - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - - if (this->m_FastMarchingFilter.IsNotNull()) - this->m_FastMarchingFilter->RemoveAllObservers(); -} - -void mitk::FastMarchingTool::ConnectActionsAndFunctions() -{ - CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); - CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); - CONNECT_FUNCTION("DeletePoint", OnDelete); -} - -const char **mitk::FastMarchingTool::GetXPM() const -{ - return nullptr; // mitkFastMarchingTool_xpm; -} - -us::ModuleResource mitk::FastMarchingTool::GetIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); - return resource; -} - -us::ModuleResource mitk::FastMarchingTool::GetCursorIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_Cursor_32x32.png"); - return resource; } const char *mitk::FastMarchingTool::GetName() const { return "2D Fast Marching"; } -void mitk::FastMarchingTool::BuildITKPipeline() -{ - m_ReferenceImageSliceAsITK = InternalImageType::New(); - - m_ReferenceImageSlice = GetAffectedReferenceSlice(m_PositionEvent); - CastToItkImage(m_ReferenceImageSlice, m_ReferenceImageSliceAsITK); - - m_ProgressCommand = mitk::ToolCommand::New(); - - m_SmoothFilter = SmoothingFilterType::New(); - m_SmoothFilter->SetInput(m_ReferenceImageSliceAsITK); - m_SmoothFilter->SetTimeStep(0.05); - m_SmoothFilter->SetNumberOfIterations(2); - m_SmoothFilter->SetConductanceParameter(9.0); - - m_GradientMagnitudeFilter = GradientFilterType::New(); - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - - m_SigmoidFilter = SigmoidFilterType::New(); - m_SigmoidFilter->SetAlpha(m_Alpha); - m_SigmoidFilter->SetBeta(m_Beta); - m_SigmoidFilter->SetOutputMinimum(0.0); - m_SigmoidFilter->SetOutputMaximum(1.0); - - m_FastMarchingFilter = FastMarchingFilterType::New(); - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - - m_ThresholdFilter = ThresholdingFilterType::New(); - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_ThresholdFilter->SetOutsideValue(0); - m_ThresholdFilter->SetInsideValue(1.0); - - m_SeedContainer = NodeContainer::New(); - m_SeedContainer->Initialize(); - m_FastMarchingFilter->SetTrialPoints(m_SeedContainer); - - if (this->m_SmoothFilter.IsNotNull()) - this->m_SmoothFilter->RemoveAllObservers(); - - if (this->m_SigmoidFilter.IsNotNull()) - this->m_SigmoidFilter->RemoveAllObservers(); - - if (this->m_GradientMagnitudeFilter.IsNotNull()) - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - - if (this->m_FastMarchingFilter.IsNotNull()) - this->m_FastMarchingFilter->RemoveAllObservers(); - - m_SmoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_GradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_FastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - - m_SmoothFilter->SetInput(m_ReferenceImageSliceAsITK); - m_GradientMagnitudeFilter->SetInput(m_SmoothFilter->GetOutput()); - m_SigmoidFilter->SetInput(m_GradientMagnitudeFilter->GetOutput()); - m_FastMarchingFilter->SetInput(m_SigmoidFilter->GetOutput()); - m_ThresholdFilter->SetInput(m_FastMarchingFilter->GetOutput()); - m_ReferenceImageSliceAsITK = InternalImageType::New(); -} - -void mitk::FastMarchingTool::SetUpperThreshold(double value) -{ - if (m_UpperThreshold != value) - { - m_UpperThreshold = value / 10.0; - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetLowerThreshold(double value) -{ - if (m_LowerThreshold != value) - { - m_LowerThreshold = value / 10.0; - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetBeta(double value) -{ - if (m_Beta != value) - { - m_Beta = value; - m_SigmoidFilter->SetBeta(m_Beta); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetSigma(double value) -{ - if (m_Sigma != value) - { - m_Sigma = value; - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetAlpha(double value) -{ - if (m_Alpha != value) - { - m_Alpha = value; - m_SigmoidFilter->SetAlpha(m_Alpha); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetStoppingValue(double value) -{ - if (m_StoppingValue != value) - { - m_StoppingValue = value; - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::Activated() -{ - Superclass::Activated(); - - m_ResultImageNode = mitk::DataNode::New(); - m_ResultImageNode->SetName("FastMarching_Preview"); - m_ResultImageNode->SetBoolProperty("helper object", true); - m_ResultImageNode->SetColor(0.0, 1.0, 0.0); - m_ResultImageNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_ResultImageNode, m_ToolManager->GetReferenceData(0)); - - m_SeedsAsPointSet = mitk::PointSet::New(); - m_SeedsAsPointSetNode = mitk::DataNode::New(); - m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_SeedsAsPointSetNode, m_ToolManager->GetReferenceData(0)); - - this->Initialize(); -} - -void mitk::FastMarchingTool::Deactivated() -{ - m_ToolManager->GetDataStorage()->Remove(this->m_ResultImageNode); - m_ToolManager->GetDataStorage()->Remove(this->m_SeedsAsPointSetNode); - this->ClearSeeds(); - m_ResultImageNode = nullptr; - m_SeedsAsPointSetNode = nullptr; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - Superclass::Deactivated(); -} - -void mitk::FastMarchingTool::Initialize() -{ - m_ReferenceImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - if (m_ReferenceImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(m_ReferenceImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - m_ReferenceImage = timeSelector->GetOutput(); - } - m_NeedUpdate = true; -} - -void mitk::FastMarchingTool::ConfirmSegmentation() -{ - // combine preview image with current working segmentation - if (dynamic_cast(m_ResultImageNode->GetData())) - { - // logical or combination of preview and segmentation slice - - Image::Pointer workingImageSlice; - - Image::Pointer workingImage = dynamic_cast(this->m_ToolManager->GetWorkingData(0)->GetData()); - TimePointType referenceImageTimePoint = m_ReferenceImage->GetTimeGeometry()->TimeStepToTimePoint(m_CurrentTimeStep); - TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(referenceImageTimePoint); - workingImageSlice = GetAffectedImageSliceAs2DImage(m_WorkingPlane, workingImage, workingImageTimeStep); - - Image::Pointer segmentationResult = Image::New(); - - bool isDeprecatedUnsignedCharSegmentation = - (workingImage->GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR); - - if (isDeprecatedUnsignedCharSegmentation) - { - typedef itk::Image OutputUCharImageType; - OutputUCharImageType::Pointer workingImageSliceInITK = OutputUCharImageType::New(); - - CastToItkImage(workingImageSlice, workingImageSliceInITK); - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput1(m_ThresholdFilter->GetOutput()); - orFilter->SetInput2(workingImageSliceInITK); - orFilter->Update(); - - mitk::CastToMitkImage(orFilter->GetOutput(), segmentationResult); - } - else - { - OutputImageType::Pointer workingImageSliceInITK = OutputImageType::New(); - - CastToItkImage(workingImageSlice, workingImageSliceInITK); - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput(0, m_ThresholdFilter->GetOutput()); - orFilter->SetInput(1, workingImageSliceInITK); - orFilter->Update(); - - mitk::CastToMitkImage(orFilter->GetOutput(), segmentationResult); - } - - segmentationResult->GetGeometry()->SetOrigin(workingImageSlice->GetGeometry()->GetOrigin()); - segmentationResult->GetGeometry()->SetIndexToWorldTransform( - workingImageSlice->GetGeometry()->GetIndexToWorldTransform()); - - // write to segmentation volume and hide preview image - // again, current time step is not considered - this->WriteBackSegmentationResult(m_WorkingPlane, segmentationResult, workingImageTimeStep); - this->m_ResultImageNode->SetVisibility(false); - - this->ClearSeeds(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::FastMarchingTool::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // Add a new seed point for FastMarching algorithm - auto *positionEvent = dynamic_cast(interactionEvent); - // const PositionEvent* p = dynamic_cast(stateEvent->GetEvent()); - if (positionEvent == nullptr) - return; - - if (m_PositionEvent.IsNotNull()) - m_PositionEvent = nullptr; - - m_PositionEvent = - InteractionPositionEvent::New(positionEvent->GetSender(), positionEvent->GetPointerPositionOnScreen()); - - // if click was on another renderwindow or slice then reset pipeline and preview - if ((m_LastEventSender != m_PositionEvent->GetSender()) || - (m_LastEventSlice != m_PositionEvent->GetSender()->GetSlice())) - { - this->BuildITKPipeline(); - this->ClearSeeds(); - } - - m_LastEventSender = m_PositionEvent->GetSender(); - m_LastEventSlice = m_LastEventSender->GetSlice(); - m_WorkingPlane = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone(); - - mitk::Point3D clickInIndex; - - m_ReferenceImageSlice->GetGeometry()->WorldToIndex(m_PositionEvent->GetPositionInWorld(), clickInIndex); - itk::Index<2> seedPosition; - seedPosition[0] = clickInIndex[0]; - seedPosition[1] = clickInIndex[1]; - - NodeType node; - const double seedValue = 0.0; - node.SetValue(seedValue); - node.SetIndex(seedPosition); - this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - m_FastMarchingFilter->Modified(); - - m_SeedsAsPointSet->InsertPoint(m_SeedsAsPointSet->GetSize(), m_PositionEvent->GetPositionInWorld()); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_NeedUpdate = true; - - this->Update(); - - m_ReadyMessage.Send(); -} - -void mitk::FastMarchingTool::OnDelete(StateMachineAction *, InteractionEvent *) -{ - // delete last seed point - if (!(this->m_SeedContainer->empty())) - { - // delete last element of seeds container - this->m_SeedContainer->pop_back(); - m_FastMarchingFilter->Modified(); - - // delete last point in pointset - somehow ugly - m_SeedsAsPointSet->GetPointSet()->GetPoints()->DeleteIndex(m_SeedsAsPointSet->GetSize() - 1); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_NeedUpdate = true; - - this->Update(); - } -} - -void mitk::FastMarchingTool::Update() -{ - const unsigned int progress_steps = 20; - - // update FastMarching pipeline and show result - if (m_NeedUpdate) - { - m_ProgressCommand->AddStepsToDo(progress_steps); - CurrentlyBusy.Send(true); - try - { - m_ThresholdFilter->Update(); - } - catch (itk::ExceptionObject &excep) - { - MITK_ERROR << "Exception caught: " << excep.GetDescription(); - - // progress by max step count, will force - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - std::string msg = excep.GetDescription(); - ErrorMessage.Send(msg); - - return; - } - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - // make output visible - mitk::Image::Pointer result = mitk::Image::New(); - CastToMitkImage(m_ThresholdFilter->GetOutput(), result); - result->GetGeometry()->SetOrigin(m_ReferenceImageSlice->GetGeometry()->GetOrigin()); - result->GetGeometry()->SetIndexToWorldTransform(m_ReferenceImageSlice->GetGeometry()->GetIndexToWorldTransform()); - m_ResultImageNode->SetData(result); - m_ResultImageNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void mitk::FastMarchingTool::ClearSeeds() -{ - // clear seeds for FastMarching as well as the PointSet for visualization - if (this->m_SeedContainer.IsNotNull()) - this->m_SeedContainer->Initialize(); - - if (this->m_SeedsAsPointSet.IsNotNull()) - { - this->m_SeedsAsPointSet = mitk::PointSet::New(); - this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - } - - if (this->m_FastMarchingFilter.IsNotNull()) - m_FastMarchingFilter->Modified(); - - this->m_NeedUpdate = true; -} - -void mitk::FastMarchingTool::Reset() -{ - // clear all seeds and preview empty result - this->ClearSeeds(); - - m_ResultImageNode->SetVisibility(false); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::FastMarchingTool::SetCurrentTimeStep(int t) -{ - if (m_CurrentTimeStep != t) - { - m_CurrentTimeStep = t; - - this->Initialize(); - } -} diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool.h index e98b2fba71..9aa06c0b1a 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool.h @@ -1,175 +1,48 @@ /*============================================================================ 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 mitkFastMarchingTool_h_Included #define mitkFastMarchingTool_h_Included -#include "mitkDataNode.h" -#include "mitkFeedbackContourTool.h" -#include "mitkPointSet.h" -#include "mitkToolCommand.h" -#include - -#include "mitkMessage.h" - -#include "itkImage.h" +#include "mitkFastMarchingBaseTool.h" -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" -#include "itkFastMarchingImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" - -namespace us -{ - class ModuleResource; -} +#include namespace mitk { - class StateMachineAction; - class InteractionEvent; - /** \brief FastMarching semgentation tool. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ - class MITKSEGMENTATION_EXPORT FastMarchingTool : public FeedbackContourTool + class MITKSEGMENTATION_EXPORT FastMarchingTool : public FastMarchingBaseTool { - mitkNewMessageMacro(Ready); - public: - mitkClassMacro(FastMarchingTool, FeedbackContourTool); + mitkClassMacro(FastMarchingTool, FastMarchingBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /* typedefs for itk pipeline */ - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - /* icon stuff */ - const char **GetXPM() const override; const char *GetName() const override; - us::ModuleResource GetCursorIconResource() const override; - us::ModuleResource GetIconResource() const override; - - /// \brief Set parameter used in Threshold filter. - void SetUpperThreshold(double); - - /// \brief Set parameter used in Threshold filter. - void SetLowerThreshold(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetStoppingValue(double); - - /// \brief Set parameter used in Gradient Magnitude filter. - void SetSigma(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetAlpha(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetBeta(double); - - /// \brief Adds the feedback image to the current working image. - virtual void ConfirmSegmentation(); - - /// \brief Set the working time step. - virtual void SetCurrentTimeStep(int t); - - /// \brief Clear all seed points. - void ClearSeeds(); - - /// \brief Updates the itk pipeline and shows the result of FastMarching. - void Update(); - protected: FastMarchingTool(); ~FastMarchingTool() override; - - void ConnectActionsAndFunctions() override; - - // virtual float CanHandleEvent( StateEvent const *stateEvent) const; - - void Activated() override; - void Deactivated() override; - virtual void Initialize(); - - virtual void BuildITKPipeline(); - - /// \brief Add point action of StateMachine pattern - virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); - - /// \brief Delete action of StateMachine pattern - virtual void OnDelete(StateMachineAction *, InteractionEvent *interactionEvent); - - /// \brief Reset all relevant inputs of the itk pipeline. - void Reset(); - - mitk::ToolCommand::Pointer m_ProgressCommand; - - Image::Pointer m_ReferenceImage; - Image::Pointer m_ReferenceImageSlice; - - bool m_NeedUpdate; - - int m_CurrentTimeStep; - - mitk::InteractionPositionEvent::Pointer m_PositionEvent; - - float m_LowerThreshold; // used in Threshold filter - float m_UpperThreshold; // used in Threshold filter - float m_StoppingValue; // used in Fast Marching filter - float m_Sigma; // used in GradientMagnitude filter - float m_Alpha; // used in Sigmoid filter - float m_Beta; // used in Sigmoid filter - - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - InternalImageType::Pointer m_ReferenceImageSliceAsITK; // the reference image as itk::Image - - mitk::DataNode::Pointer m_ResultImageNode; // holds the result as a preview image - - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; - - ThresholdingFilterType::Pointer m_ThresholdFilter; - SmoothingFilterType::Pointer m_SmoothFilter; - GradientFilterType::Pointer m_GradientMagnitudeFilter; - SigmoidFilterType::Pointer m_SigmoidFilter; - FastMarchingFilterType::Pointer m_FastMarchingFilter; - - private: - PlaneGeometry::Pointer m_WorkingPlane; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp index 8ada9faf2b..4dc155ad03 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp @@ -1,334 +1,33 @@ /*============================================================================ 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 "mitkFastMarchingTool3D.h" -#include "mitkToolManager.h" - -#include "mitkBaseRenderer.h" -#include "mitkInteractionConst.h" -#include "mitkRenderingManager.h" - -#include "mitkImageAccessByItk.h" - -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" - -// us -#include -#include -#include -#include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool3D, "FastMarching3D tool"); } mitk::FastMarchingTool3D::FastMarchingTool3D() - : AutoSegmentationWithPreviewTool(), - m_LowerThreshold(0), - m_UpperThreshold(200), - m_StoppingValue(100), - m_Sigma(1.0), - m_Alpha(-0.5), - m_Beta(3.0), - m_PointSetAddObserverTag(0), - m_PointSetRemoveObserverTag(0) + : FastMarchingBaseTool() { + TOOL_DIMENSION = 3; } mitk::FastMarchingTool3D::~FastMarchingTool3D() { } -bool mitk::FastMarchingTool3D::CanHandle(const BaseData* referenceData, const BaseData* workingData) const -{ - if(!Superclass::CanHandle(referenceData, workingData)) - return false; - - if (referenceData == nullptr) - return false; - - auto *image = dynamic_cast(referenceData); - - if (image == nullptr) - return false; - - if (image->GetDimension() < 3) - return false; - - return true; -} - -const char **mitk::FastMarchingTool3D::GetXPM() const -{ - return nullptr; // mitkFastMarchingTool3D_xpm; -} - -us::ModuleResource mitk::FastMarchingTool3D::GetIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); - return resource; -} - const char *mitk::FastMarchingTool3D::GetName() const { return "Fast Marching 3D"; } - -void mitk::FastMarchingTool3D::SetUpperThreshold(double value) -{ - m_UpperThreshold = value / 10.0; -} - -void mitk::FastMarchingTool3D::SetLowerThreshold(double value) -{ - m_LowerThreshold = value / 10.0; -} - -void mitk::FastMarchingTool3D::SetBeta(double value) -{ - if (m_Beta != value) - { - m_Beta = value; - } -} - -void mitk::FastMarchingTool3D::SetSigma(double value) -{ - if (m_Sigma != value) - { - if (value > 0.0) - { - m_Sigma = value; - } - } -} - -void mitk::FastMarchingTool3D::SetAlpha(double value) -{ - if (m_Alpha != value) - { - m_Alpha = value; - } -} - -void mitk::FastMarchingTool3D::SetStoppingValue(double value) -{ - if (m_StoppingValue != value) - { - m_StoppingValue = value; - } -} - -void mitk::FastMarchingTool3D::Activated() -{ - Superclass::Activated(); - - m_SeedsAsPointSet = mitk::PointSet::New(); - m_SeedsAsPointSetNode = mitk::DataNode::New(); - m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("3D_FastMarching_PointSet"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - - // Create PointSetData Interactor - m_SeedPointInteractor = mitk::PointSetDataInteractor::New(); - // Load the according state machine for regular point set interaction - m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); - // Set the configuration file that defines the triggers for the transitions - m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); - // set the DataNode (which already is added to the DataStorage - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); - - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); - - m_SeedContainer = NodeContainer::New(); - m_SeedContainer->Initialize(); - - itk::SimpleMemberCommand::Pointer pointAddedCommand = - itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); - m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - - itk::SimpleMemberCommand::Pointer pointRemovedCommand = - itk::SimpleMemberCommand::New(); - pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); - m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); -} - -void mitk::FastMarchingTool3D::Deactivated() -{ - this->ClearSeeds(); - - // Deactivate Interaction - m_SeedPointInteractor->SetDataNode(nullptr); - m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); - m_SeedsAsPointSetNode = nullptr; - m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); - m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); - m_SeedsAsPointSet = nullptr; - - Superclass::Deactivated(); -} - -void mitk::FastMarchingTool3D::OnAddPoint() -{ - // Add a new seed point for FastMarching algorithm - mitk::Point3D clickInIndex; - - this->GetReferenceData()->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), - clickInIndex); - itk::Index<3> seedPosition; - seedPosition[0] = clickInIndex[0]; - seedPosition[1] = clickInIndex[1]; - seedPosition[2] = clickInIndex[2]; - - NodeType node; - const double seedValue = 0.0; - node.SetValue(seedValue); - node.SetIndex(seedPosition); - this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - this->UpdatePreview(); -} - -void mitk::FastMarchingTool3D::OnDelete() -{ - // delete last seed point - if (!(this->m_SeedContainer->empty())) - { - // delete last element of seeds container - this->m_SeedContainer->pop_back(); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - this->UpdatePreview(); - } -} - -void mitk::FastMarchingTool3D::ClearSeeds() -{ - // clear seeds for FastMarching as well as the PointSet for visualization - if (this->m_SeedContainer.IsNotNull()) - this->m_SeedContainer->Initialize(); - - if (this->m_SeedsAsPointSet.IsNotNull()) - { - // remove observers from current pointset - m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); - m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); - - // renew pointset - this->m_SeedsAsPointSet = mitk::PointSet::New(); - this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - - // add callback function for adding and removing points - itk::SimpleMemberCommand::Pointer pointAddedCommand = - itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); - m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - - itk::SimpleMemberCommand::Pointer pointRemovedCommand = - itk::SimpleMemberCommand::New(); - pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); - m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); - } -} - -template -void mitk::FastMarchingTool3D::DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep) -{ - typedef itk::Image InputImageType; - - /* typedefs for itk pipeline */ - - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - - auto smoothFilter = SmoothingFilterType::New(); - smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - smoothFilter->SetTimeStep(0.05); - smoothFilter->SetNumberOfIterations(2); - smoothFilter->SetConductanceParameter(9.0); - - auto gradientMagnitudeFilter = GradientFilterType::New(); - gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - gradientMagnitudeFilter->SetSigma(m_Sigma); - - auto sigmoidFilter = SigmoidFilterType::New(); - sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - sigmoidFilter->SetAlpha(m_Alpha); - sigmoidFilter->SetBeta(m_Beta); - sigmoidFilter->SetOutputMinimum(0.0); - sigmoidFilter->SetOutputMaximum(1.0); - - auto fastMarchingFilter = FastMarchingFilterType::New(); - fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - fastMarchingFilter->SetStoppingValue(m_StoppingValue); - fastMarchingFilter->SetTrialPoints(m_SeedContainer); - - auto thresholdFilter = ThresholdingFilterType::New(); - thresholdFilter->SetLowerThreshold(m_LowerThreshold); - thresholdFilter->SetUpperThreshold(m_UpperThreshold); - thresholdFilter->SetOutsideValue(0); - thresholdFilter->SetInsideValue(1.0); - - // set up pipeline - smoothFilter->SetInput(inputImage); - gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); - sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); - fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); - thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); - thresholdFilter->Update(); - - segmentation->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::FastMarchingTool3D::UpdatePrepare() -{ - // remove interaction with poinset while updating - if (m_SeedPointInteractor.IsNotNull()) - m_SeedPointInteractor->SetDataNode(nullptr); -} - -void mitk::FastMarchingTool3D::UpdateCleanUp() -{ - // add interaction with poinset again - if (m_SeedPointInteractor.IsNotNull()) - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); -} - -void mitk::FastMarchingTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) -{ - if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedContainer.IsNotNull()) - { - AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep)); - } -} diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h index 652e0d550b..88b82a6038 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h @@ -1,124 +1,49 @@ /*============================================================================ 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 mitkFastMarchingTool3D_h_Included #define mitkFastMarchingTool3D_h_Included -#include "mitkAutoSegmentationWithPreviewTool.h" -#include "mitkDataNode.h" -#include "mitkPointSet.h" -#include "mitkPointSetDataInteractor.h" -#include "mitkToolCommand.h" - -#include "itkImage.h" -#include "itkFastMarchingImageFilter.h" +#include "mitkFastMarchingBaseTool.h" #include -namespace us -{ - class ModuleResource; -} - namespace mitk { /** \brief FastMarching semgentation tool. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ - class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public FastMarchingBaseTool { public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); + mitkClassMacro(FastMarchingTool3D, FastMarchingBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; - /* icon stuff */ - const char **GetXPM() const override; const char *GetName() const override; - us::ModuleResource GetIconResource() const override; - - void Activated() override; - void Deactivated() override; - - /// \brief Set parameter used in Threshold filter. - void SetUpperThreshold(double); - - /// \brief Set parameter used in Threshold filter. - void SetLowerThreshold(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetStoppingValue(double); - - /// \brief Set parameter used in Gradient Magnitude filter. - void SetSigma(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetAlpha(double); - - /// \brief Set parameter used in Fast Marching filter. - void SetBeta(double); - - /// \brief Clear all seed points. - void ClearSeeds(); protected: FastMarchingTool3D(); ~FastMarchingTool3D() override; - - /// \brief Add point action of StateMachine pattern - virtual void OnAddPoint(); - - /// \brief Delete action of StateMachine pattern - virtual void OnDelete(); - - void UpdatePrepare() override; - void UpdateCleanUp() override; - void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - - template - void DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep); - - float m_LowerThreshold; // used in Threshold filter - float m_UpperThreshold; // used in Threshold filter - float m_StoppingValue; // used in Fast Marching filter - float m_Sigma; // used in GradientMagnitude filter - float m_Alpha; // used in Sigmoid filter - float m_Beta; // used in Sigmoid filter - - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; - mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; - unsigned int m_PointSetAddObserverTag; - unsigned int m_PointSetRemoveObserverTag; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index e6d75a4636..b560307d1b 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,754 +1,798 @@ /*============================================================================ 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 "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" #include "mitkExtractDirectedPlaneImageFilter.h" #include "mitkExtractImageFilter.h" // Include of the new ImageExtractor #include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkMorphologicalOperations.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageToItk.h" #include "mitkLabelSetImage.h" #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigMITKNoCrosshair.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // frontal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contourNormal = sliceInfo.plane->GetNormal(); contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection) { if (!m_SurfaceInterpolationEnabled) return; //Remark the ImageTimeSelector is just needed to extract a timestep/channel of //of the image in order to get the image dimension (time dimension and channel dimension stripped away). //Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { RemoveContourFromInterpolator(sliceInfo); } else { contour->DisconnectPipeline(); contourList.push_back(contour); } } mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); m_ToolManager->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = 0.; } void mitk::SegTool2D::Deactivated() { m_ToolManager->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { return m_ToolManager->GetWorkingData(0); } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { return m_ToolManager->GetReferenceData(0); } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } -void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, Image *slice) +void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); - if (planeGeometry && slice && !abstractTransformGeometry) + if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); - this->WriteBackSegmentationResult(planeGeometry, slice, timeStep); + this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } +void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) +{ + if (!planeGeometry || !segmentationResult) + return; + + SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); + WriteBackSegmentationResults(workingNode, { sliceInfo }, true); +} + void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, - Image *slice, + const Image * segmentationResult, TimeStepType timeStep) { - if (!planeGeometry || !slice) + if (!planeGeometry || !segmentationResult) return; - SliceInformation sliceInfo(slice, const_cast(planeGeometry), timeStep); - WriteBackSegmentationResults({ sliceInfo }); + SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); + WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume) { const auto workingNode = this->GetWorkingDataNode(); + + mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); + + // the first geometry is needed otherwise restoring the position is not working + const auto* plane3 = + dynamic_cast(dynamic_cast( + m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) + ->GetPlaneGeometry(0)); + unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); + + if (m_SurfaceInterpolationEnabled) + { + for (const auto& sliceInfo : sliceList) + { + this->AddContourmarker(plane3, slicePosition); + } + } + +} + +void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) +{ + if (nullptr == workingNode) + { + mitkThrow() << "Cannot write slice to working node. Working node is invalid."; + } + auto* image = dynamic_cast(workingNode->GetData()); + if (nullptr == image) + { + mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; + } + for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { - this->WriteSliceToVolume(sliceInfo); + mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } - this->UpdateSurfaceInterpolation(sliceList, image, false); + mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); - if (m_SurfaceInterpolationEnabled) - this->AddContourmarker(); + // also mark its node as modified (T27308). Can be removed if T27307 + // is properly solved + if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } -void mitk::SegTool2D::WriteSliceToVolume(const mitk::SegTool2D::SliceInformation &sliceInfo) +void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { - const auto workingNode = this->GetWorkingDataNode(); - auto *image = dynamic_cast(workingNode->GetData()); - - /*============= BEGIN undo/redo feature block ========================*/ - // Create undo operation by caching the not yet modified slices - mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, image, sliceInfo.timestep); - auto *undoOperation = - new DiffSliceOperation(image, - originalSlice, - dynamic_cast(originalSlice->GetGeometry()), - sliceInfo.timestep, - sliceInfo.plane); - /*============= END undo/redo feature block ========================*/ + SliceInformation sliceInfo(slice, planeGeometry, timeStep); + WriteSliceToVolume(workingImage, sliceInfo , true); +} + +void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) +{ + if (nullptr == workingImage) + { + mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; + } + + DiffSliceOperation* undoOperation = nullptr; + + if (allowUndo) + { + /*============= BEGIN undo/redo feature block ========================*/ + // Create undo operation by caching the not yet modified slices + mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); + undoOperation = + new DiffSliceOperation(workingImage, + originalSlice, + dynamic_cast(originalSlice->GetGeometry()), + sliceInfo.timestep, + sliceInfo.plane); + /*============= END undo/redo feature block ========================*/ + } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); - extractor->SetInput(image); + extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); - extractor->SetResliceTransformByGeometry(image->GetGeometry(sliceInfo.timestep)); + extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so - image->Modified(); - image->GetVtkImageData()->Modified(); + workingImage->Modified(); + workingImage->GetVtkImageData()->Modified(); - // also mark its node as modified (T27308). Can be removed if T27307 - // is properly solved - if (workingNode != nullptr) workingNode->Modified(); - - /*============= BEGIN undo/redo feature block ========================*/ - // specify the undo operation with the edited slice - auto *doOperation = - new DiffSliceOperation(image, - extractor->GetOutput(), - dynamic_cast(sliceInfo.slice->GetGeometry()), - sliceInfo.timestep, - sliceInfo.plane); - - // create an operation event for the undo stack - OperationEvent *undoStackItem = - new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); - - // add it to the undo controller - UndoStackItem::IncCurrObjectEventId(); - UndoStackItem::IncCurrGroupEventId(); - UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); + if (allowUndo) + { + /*============= BEGIN undo/redo feature block ========================*/ + // specify the redo operation with the edited slice + auto* doOperation = + new DiffSliceOperation(workingImage, + extractor->GetOutput(), + dynamic_cast(sliceInfo.slice->GetGeometry()), + sliceInfo.timestep, + sliceInfo.plane); + + // create an operation event for the undo stack + OperationEvent* undoStackItem = + new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); + + // add it to the undo controller + UndoStackItem::IncCurrObjectEventId(); + UndoStackItem::IncCurrGroupEventId(); + UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); + /*============= END undo/redo feature block ========================*/ + } - // clear the pointers as the operation are stored in the undocontroller and also deleted from there - undoOperation = nullptr; - doOperation = nullptr; - /*============= END undo/redo feature block ========================*/ } + void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } -int mitk::SegTool2D::AddContourmarker() +int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { - if (m_LastEventSender == nullptr) + if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); - unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); - - // the first geometry is needed otherwise restoring the position is not working - const auto *plane = - dynamic_cast(dynamic_cast( - m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) - ->GetPlaneGeometry(0)); - unsigned int size = service->GetNumberOfPlanePositions(); - unsigned int id = service->AddNewPlanePosition(plane, slicePosition); + unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; - plane->Map(plane->GetCenter(), p1); + planeGeometry->Map(planeGeometry->GetCenter(), p1); mitk::Point2D p2 = p1; - p2[0] -= plane->GetSpacing()[0]; - p2[1] -= plane->GetSpacing()[1]; + p2[0] -= planeGeometry->GetSpacing()[0]; + p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); - contourMarker->SetPlaneGeometry(const_cast(plane)); + contourMarker->SetPlaneGeometry(planeGeometry->Clone()); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); - if (plane) + if (planeGeometry) { if (id == size) { m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = m_ToolManager->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } template void InternalWritePreviewOnWorkingImage(itk::Image *targetSlice, const mitk::Image *sourceSlice, mitk::Image *originalImage, int overwritevalue) { typedef itk::Image SliceType; typename SliceType::Pointer sourceSliceITK; CastToItkImage(sourceSlice, sourceSliceITK); // now the original slice and the ipSegmentation-painted slice are in the same format, and we can just copy all pixels // that are non-zero typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator InputIteratorType; InputIteratorType inputIterator(sourceSliceITK, sourceSliceITK->GetLargestPossibleRegion()); OutputIteratorType outputIterator(targetSlice, targetSlice->GetLargestPossibleRegion()); outputIterator.GoToBegin(); inputIterator.GoToBegin(); auto *workingImage = dynamic_cast(originalImage); assert(workingImage); int activePixelValue = workingImage->GetActiveLabel()->GetValue(); if (activePixelValue == 0) // if exterior is the active label { while (!outputIterator.IsAtEnd()) { if (inputIterator.Get() != 0) { outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else if (overwritevalue != 0) // if we are not erasing { while (!outputIterator.IsAtEnd()) { auto targetValue = static_cast(outputIterator.Get()); if (inputIterator.Get() != 0) { if (!workingImage->GetLabel(targetValue)->GetLocked()) { outputIterator.Set(overwritevalue); } } if (targetValue == overwritevalue) { outputIterator.Set(inputIterator.Get()); } ++outputIterator; ++inputIterator; } } else // if we are erasing { while (!outputIterator.IsAtEnd()) { const int targetValue = outputIterator.Get(); if (inputIterator.Get() != 0) { if (targetValue == activePixelValue) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, Image *sourceSlice, mitk::Image *workingImage, int paintingPixelValue, int) { if ((!targetSlice) || (!sourceSlice)) return; AccessFixedDimensionByItk_3( targetSlice, InternalWritePreviewOnWorkingImage, 2, sourceSlice, workingImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index f7c71b406f..abb8bafc41 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,254 +1,261 @@ /*============================================================================ 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 mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkTool.h" #include #include "mitkInteractionPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection); /** * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of a dwi-image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a dwi-image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); + static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); + + static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); + + void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ mitk::DataNode* GetWorkingDataNode() const; mitk::Image* GetWorkingData() const; mitk::DataNode* GetReferenceDataNode() const; mitk::Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane; mitk::TimeStepType timestep; SliceInformation() {} SliceInformation(const mitk::Image *slice, const mitk::PlaneGeometry *plane, mitk::TimeStepType timestep) { this->slice = slice; this->plane = plane; this->timestep = timestep; } }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection); /** * \brief Filters events that cannot be handle by 2D segmentation tools * * Current an event is discarded if it was not sent by a 2D renderwindow and if it is * not of type InteractionPositionEvent */ bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const InteractionPositionEvent *) const; /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; - void WriteBackSegmentationResult(const InteractionPositionEvent *, Image *); + void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); - void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, Image *, TimeStepType timeStep); + void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); void WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume = true); + static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume = true); void WritePreviewOnWorkingImage( Image *targetSlice, Image *sourceSlice, Image *workingImage, int paintingPixelValue, int timestep); - void WriteSliceToVolume(const SliceInformation &sliceInfo); + static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); + /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. - By selecting this node the slicestack will be reoriented according to the PlanarFigure's Geometry + By selecting this node the slicestack will be reoriented according to passed the PlanarFigure's Geometry */ - int AddContourmarker(); + int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp index f02ff0523b..40bafac7b8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp @@ -1,350 +1,342 @@ /*============================================================================ 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 "QmitkFastMarchingToolGUI.h" #include "QmitkNewSegmentationDialog.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingToolGUI, "") -QmitkFastMarchingToolGUI::QmitkFastMarchingToolGUI() : QmitkToolGUI(), m_TimeIsConnected(false) +QmitkFastMarchingToolGUI::QmitkFastMarchingToolGUI() : QmitkToolGUI() { this->setContentsMargins(0, 0, 0, 0); // create the visible widgets QVBoxLayout *widgetLayout = new QVBoxLayout(this); widgetLayout->setContentsMargins(0, 0, 0, 0); QFont fntHelp; fntHelp.setBold(true); QLabel *lblHelp = new QLabel(this); lblHelp->setText("Press shift-click to add seeds repeatedly."); lblHelp->setFont(fntHelp); widgetLayout->addWidget(lblHelp); // Sigma controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Sigma: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slSigma = new ctkSliderWidget(this); m_slSigma->setMinimum(0.1); m_slSigma->setMaximum(5.0); m_slSigma->setPageStep(0.1); m_slSigma->setSingleStep(0.01); m_slSigma->setValue(1.0); m_slSigma->setTracking(false); m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); widgetLayout->addWidget(m_slSigma); // Alpha controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Alpha: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slAlpha = new ctkSliderWidget(this); m_slAlpha->setMinimum(-10); m_slAlpha->setMaximum(0); m_slAlpha->setPageStep(0.1); m_slAlpha->setSingleStep(0.01); m_slAlpha->setValue(-2.5); m_slAlpha->setTracking(false); m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); widgetLayout->addWidget(m_slAlpha); // Beta controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Beta: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slBeta = new ctkSliderWidget(this); m_slBeta->setMinimum(0); m_slBeta->setMaximum(100); m_slBeta->setPageStep(0.1); m_slBeta->setSingleStep(0.01); m_slBeta->setValue(3.5); m_slBeta->setTracking(false); m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); widgetLayout->addWidget(m_slBeta); // stopping value controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Stopping value: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slStoppingValue = new ctkSliderWidget(this); m_slStoppingValue->setMinimum(0); m_slStoppingValue->setMaximum(10000); m_slStoppingValue->setPageStep(10); m_slStoppingValue->setSingleStep(1); m_slStoppingValue->setValue(2000); m_slStoppingValue->setDecimals(0); m_slStoppingValue->setTracking(false); m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); widgetLayout->addWidget(m_slStoppingValue); // threshold controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Threshold: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slwThreshold = new ctkRangeWidget(this); m_slwThreshold->setMinimum(-100); m_slwThreshold->setMaximum(5000); m_slwThreshold->setMinimumValue(-100); m_slwThreshold->setMaximumValue(2000); m_slwThreshold->setDecimals(0); m_slwThreshold->setTracking(false); m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); widgetLayout->addWidget(m_slwThreshold); m_btClearSeeds = new QPushButton("Clear"); m_btClearSeeds->setToolTip("Clear current result and start over again"); m_btClearSeeds->setEnabled(false); widgetLayout->addWidget(m_btClearSeeds); connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); m_btConfirm = new QPushButton("Confirm Segmentation"); m_btConfirm->setToolTip("Incorporate current result in your working session."); m_btConfirm->setEnabled(false); widgetLayout->addWidget(m_btConfirm); connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); m_slAlpha->setDecimals(2); m_slSigma->setDecimals(2); m_slBeta->setDecimals(2); this->EnableWidgets(false); } QmitkFastMarchingToolGUI::~QmitkFastMarchingToolGUI() { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); } } void QmitkFastMarchingToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); } m_FastMarchingTool = dynamic_cast(tool); if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->AddReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); - - // listen to timestep change events - mitk::BaseRenderer::Pointer renderer; - renderer = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - if (renderer.IsNotNull() && !m_TimeIsConnected) - { - new QmitkStepperAdapter(this, renderer->GetSliceNavigationController()->GetTime(), "stepper"); - // connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(Refetch())); - m_TimeIsConnected = true; - } + + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->SetOverwriteExistingSegmentation(true); + m_FastMarchingTool->ClearSeeds(); + //m_CheckProcessAll->setVisible(m_FastMarchingTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkFastMarchingToolGUI::Update() { - m_FastMarchingTool->SetLowerThreshold(this->m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(this->m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(this->m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(this->m_slSigma->value()); - m_FastMarchingTool->SetAlpha(this->m_slAlpha->value()); - m_FastMarchingTool->SetBeta(this->m_slBeta->value()); - m_FastMarchingTool->Update(); + if (m_FastMarchingTool.IsNotNull()) + { + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->UpdatePreview(); + } } void QmitkFastMarchingToolGUI::OnThresholdChanged(double lower, double upper) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetLowerThreshold(lower); m_FastMarchingTool->SetUpperThreshold(upper); this->Update(); } } void QmitkFastMarchingToolGUI::OnBetaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetBeta(value); this->Update(); } } void QmitkFastMarchingToolGUI::OnSigmaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetSigma(value); this->Update(); } } void QmitkFastMarchingToolGUI::OnAlphaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetAlpha(value); this->Update(); } } void QmitkFastMarchingToolGUI::OnStoppingValueChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetStoppingValue(value); this->Update(); } } void QmitkFastMarchingToolGUI::OnConfirmSegmentation() { if (m_FastMarchingTool.IsNotNull()) { + m_FastMarchingTool->SetOverwriteExistingSegmentation(true); + m_FastMarchingTool->SetCreateAllTimeSteps(false);// m_CheckProcessAll->isChecked()); + m_btConfirm->setEnabled(false); m_FastMarchingTool->ConfirmSegmentation(); } } -void QmitkFastMarchingToolGUI::SetStepper(mitk::Stepper *stepper) -{ - this->m_TimeStepper = stepper; -} - -void QmitkFastMarchingToolGUI::Refetch() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->SetCurrentTimeStep(m_TimeStepper->GetPos()); -} - void QmitkFastMarchingToolGUI::OnClearSeeds() { // event from image navigator recieved - timestep has changed m_FastMarchingTool->ClearSeeds(); m_btClearSeeds->setEnabled(false); m_btConfirm->setEnabled(false); this->EnableWidgets(false); this->Update(); } void QmitkFastMarchingToolGUI::BusyStateChanged(bool value) { if (value) + { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + this->EnableWidgets(false); + } else + { QApplication::restoreOverrideCursor(); -} - -void QmitkFastMarchingToolGUI::OnFastMarchingToolReady() -{ - this->EnableWidgets(true); - this->m_btClearSeeds->setEnabled(true); - this->m_btConfirm->setEnabled(true); + this->EnableWidgets(true); + } } void QmitkFastMarchingToolGUI::EnableWidgets(bool enable) { m_slSigma->setEnabled(enable); m_slAlpha->setEnabled(enable); m_slBeta->setEnabled(enable); m_slStoppingValue->setEnabled(enable); m_slwThreshold->setEnabled(enable); + m_btClearSeeds->setEnabled(enable); + m_btConfirm->setEnabled(enable); +// m_CheckCreateNew->setEnabled(enable); +// m_CheckProcessAll->setEnabled(enable); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h index 6d1ede7b3d..db0d5d69e6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h @@ -1,84 +1,77 @@ /*============================================================================ 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 QmitkFastMarchingToolGUI_h_Included #define QmitkFastMarchingToolGUI_h_Included #include "QmitkToolGUI.h" #include "mitkFastMarchingTool.h" #include class ctkSliderWidget; class ctkRangeWidget; class QPushButton; #include "QmitkStepperAdapter.h" /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::FastMarchingTool. \sa mitk::FastMarchingTool */ class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkFastMarchingToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdChanged(int current); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnThresholdChanged(double, double); void OnAlphaChanged(double); void OnBetaChanged(double); void OnSigmaChanged(double); void OnStoppingValueChanged(double); void OnConfirmSegmentation(); - void Refetch(); - void SetStepper(mitk::Stepper *); void OnClearSeeds(); protected: QmitkFastMarchingToolGUI(); ~QmitkFastMarchingToolGUI() override; void Update(); void BusyStateChanged(bool) override; ctkRangeWidget *m_slwThreshold; ctkSliderWidget *m_slStoppingValue; ctkSliderWidget *m_slSigma; ctkSliderWidget *m_slAlpha; ctkSliderWidget *m_slBeta; QPushButton *m_btConfirm; QPushButton *m_btClearSeeds; mitk::FastMarchingTool::Pointer m_FastMarchingTool; - bool m_TimeIsConnected; - mitk::Stepper::Pointer m_TimeStepper; - - void OnFastMarchingToolReady(); - private: void EnableWidgets(bool); }; #endif