diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp index 5ade4cb9f4..0dc58b626e 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp @@ -1,418 +1,480 @@ /*============================================================================ 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 "mitkBinaryThresholdTool.h" #include "mitkBoundingObjectToSegmentationFilter.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkDataStorage.h" #include "mitkLevelWindowProperty.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkVtkResliceInterpolationProperty.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include #include // us #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleResource.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, BinaryThresholdTool, "Thresholding tool"); } mitk::BinaryThresholdTool::BinaryThresholdTool() : m_SensibleMinimumThresholdValue(-100), m_SensibleMaximumThresholdValue(+100), m_CurrentThresholdValue(0.0), m_IsFloatImage(false) { m_ThresholdFeedbackNode = DataNode::New(); m_ThresholdFeedbackNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_ThresholdFeedbackNode->SetProperty("name", StringProperty::New("Thresholding feedback")); m_ThresholdFeedbackNode->SetProperty("opacity", FloatProperty::New(0.3)); m_ThresholdFeedbackNode->SetProperty("binary", BoolProperty::New(true)); m_ThresholdFeedbackNode->SetProperty("helper object", BoolProperty::New(true)); } mitk::BinaryThresholdTool::~BinaryThresholdTool() { } const char **mitk::BinaryThresholdTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::BinaryThresholdTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Threshold_48x48.png"); return resource; } const char *mitk::BinaryThresholdTool::GetName() const { return "Threshold"; } void mitk::BinaryThresholdTool::Activated() { Superclass::Activated(); m_ToolManager->RoiDataChanged += mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnRoiDataChanged); + m_ToolManager->SelectedTimePointChanged += + mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnTimePointChanged); + m_OriginalImageNode = m_ToolManager->GetReferenceData(0); m_NodeForThresholding = m_OriginalImageNode; if (m_NodeForThresholding.IsNotNull()) { SetupPreviewNode(); } else { m_ToolManager->ActivateTool(-1); } } void mitk::BinaryThresholdTool::Deactivated() { m_ToolManager->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged -= + mitk::MessageDelegate(this, &mitk::BinaryThresholdTool::OnTimePointChanged); + m_NodeForThresholding = nullptr; m_OriginalImageNode = nullptr; try { if (DataStorage *storage = m_ToolManager->GetDataStorage()) { storage->Remove(m_ThresholdFeedbackNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } m_ThresholdFeedbackNode->SetData(nullptr); Superclass::Deactivated(); } void mitk::BinaryThresholdTool::SetThresholdValue(double value) { if (m_ThresholdFeedbackNode.IsNotNull()) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (value < m_SensibleMinimumThresholdValue || value > m_SensibleMaximumThresholdValue) { return; } m_CurrentThresholdValue = value; // Bug 19250: The range of 0.01 is rather random. It was 0.001 before and probably due to rounding error propagation // in VTK code // it leads to strange banding effects on floating point images with a huge range (like -40000 - 40000). 0.01 lowers // this effect // enough to work with our images. Might not work on images with really huge ranges, though. Anyways, still seems to // be low enough // to work for floating point images with a range between 0 and 1. A better solution might be to dynamically // calculate the value // based on the value range of the current image (as big as possible, as small as necessary). // m_ThresholdFeedbackNode->SetProperty( "levelwindow", LevelWindowProperty::New( // LevelWindow(m_CurrentThresholdValue, 0.01) ) ); UpdatePreview(); } } void mitk::BinaryThresholdTool::AcceptCurrentThresholdValue() { - CreateNewSegmentationFromThreshold(m_NodeForThresholding); + CreateNewSegmentationFromThreshold(); RenderingManager::GetInstance()->RequestUpdateAll(); m_ToolManager->ActivateTool(-1); } void mitk::BinaryThresholdTool::CancelThresholding() { m_ToolManager->ActivateTool(-1); } void mitk::BinaryThresholdTool::SetupPreviewNode() { itk::RGBPixel pixel; pixel[0] = 0.0f; pixel[1] = 1.0f; pixel[2] = 0.0f; if (m_NodeForThresholding.IsNotNull()) { Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); Image::Pointer originalImage = dynamic_cast(m_OriginalImageNode->GetData()); if (image.IsNotNull()) { mitk::LabelSetImage::Pointer workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { m_ThresholdFeedbackNode->SetData(workingImage->Clone()); m_IsOldBinary = false; // Let's paint the feedback node green... mitk::LabelSetImage::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); if (previewImage.IsNull()) { MITK_ERROR << "Cannot create helper objects."; return; } previewImage->GetActiveLabel()->SetColor(pixel); previewImage->GetActiveLabelSet()->UpdateLookupTable(previewImage->GetActiveLabel()->GetValue()); } else { mitk::Image::Pointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImageBin) { m_ThresholdFeedbackNode->SetData(workingImageBin->Clone()); m_IsOldBinary = true; } else m_ThresholdFeedbackNode->SetData(mitk::Image::New()); } m_ThresholdFeedbackNode->SetColor(pixel); m_ThresholdFeedbackNode->SetOpacity(0.5); int layer(50); m_NodeForThresholding->GetIntProperty("layer", layer); m_ThresholdFeedbackNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = m_ToolManager->GetDataStorage()) { if (!ds->Exists(m_ThresholdFeedbackNode)) ds->Add(m_ThresholdFeedbackNode, m_OriginalImageNode); } if (image.GetPointer() == originalImage.GetPointer()) { + m_SensibleMinimumThresholdValue = std::numeric_limits::max(); + m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); Image::StatisticsHolderPointer statistics = originalImage->GetStatistics(); - m_SensibleMinimumThresholdValue = static_cast(statistics->GetScalarValueMin()); - m_SensibleMaximumThresholdValue = static_cast(statistics->GetScalarValueMax()); + for (unsigned int ts = 0; ts < originalImage->GetTimeSteps(); ++ts) + { + m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); + m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); + } } if ((originalImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && (originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) m_IsFloatImage = true; else m_IsFloatImage = false; m_CurrentThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, m_IsFloatImage); ThresholdingValueChanged.Send(m_CurrentThresholdValue); } } } template -static void ITKSetVolume(itk::Image *originalImage, +static void ITKSetVolume(const itk::Image *originalImage, mitk::Image *segmentation, unsigned int timeStep) { - segmentation->SetVolume((void *)originalImage->GetPixelContainer()->GetBufferPointer(), 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::BinaryThresholdTool::CreateNewSegmentationFromThreshold(DataNode *node) +void mitk::BinaryThresholdTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { - if (node) + try + { + Image::ConstPointer image3D = this->Get3DImage(sourceImage, timeStep); + + if (image3D->GetDimension() == 2) + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 2, destinationImage, timeStep); + } + else + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 3, destinationImage, timeStep); + } + } + catch (...) + { + Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); + } +} + +void mitk::BinaryThresholdTool::CreateNewSegmentationFromThreshold() +{ + if (m_NodeForThresholding.IsNotNull() && m_ThresholdFeedbackNode.IsNotNull()) { Image::Pointer feedBackImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); if (feedBackImage.IsNotNull()) { - DataNode::Pointer emptySegmentation = GetTargetSegmentationNode(); + DataNode::Pointer emptySegmentationNode = GetTargetSegmentationNode(); - if (emptySegmentation) + if (emptySegmentationNode) { - // actually perform a thresholding and ask for an organ type - for (unsigned int timeStep = 0; timeStep < feedBackImage->GetTimeSteps(); ++timeStep) + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto emptySegmentation = dynamic_cast(emptySegmentationNode->GetData()); + + // actually perform a thresholding + // REMARK: the following code in this scope assumes that feedBackImage and emptySegmentationImage + // are clones of the working image (segmentation provided to the tool). Therefor the have the same + // time geometry. + if (feedBackImage->GetTimeSteps() != emptySegmentation->GetTimeSteps()) { - try - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(feedBackImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer image3D = timeSelector->GetOutput(); - - if (image3D->GetDimension() == 2) - { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - else - { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, dynamic_cast(emptySegmentation->GetData()), timeStep); - } - } - catch (...) + mitkThrow() << "Cannot performe 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 < feedBackImage->GetTimeSteps(); ++timeStep) { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); + TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); } } + else + { + const auto timeStep = emptySegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); + } if (m_OriginalImageNode.GetPointer() != m_NodeForThresholding.GetPointer()) { mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); - padFilter->SetInput(0, dynamic_cast(emptySegmentation->GetData())); + padFilter->SetInput(0, emptySegmentation); padFilter->SetInput(1, dynamic_cast(m_OriginalImageNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); - emptySegmentation->SetData(padFilter->GetOutput()); + emptySegmentationNode->SetData(padFilter->GetOutput()); } - m_ToolManager->SetWorkingData(emptySegmentation); + m_ToolManager->SetWorkingData(emptySegmentationNode); m_ToolManager->GetWorkingData(0)->Modified(); } } } } void mitk::BinaryThresholdTool::OnRoiDataChanged() { mitk::DataNode::Pointer node = m_ToolManager->GetRoiData(0); if (node.IsNotNull()) { mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); mitk::Image::Pointer image = dynamic_cast(m_NodeForThresholding->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_SensibleMaximumThresholdValue = static_cast(roiFilter->GetMaxValue()); m_SensibleMinimumThresholdValue = static_cast(roiFilter->GetMinValue()); m_NodeForThresholding = tmpNode; } else { m_NodeForThresholding = m_OriginalImageNode; } this->SetupPreviewNode(); this->UpdatePreview(); } +void mitk::BinaryThresholdTool::OnTimePointChanged() +{ + if (m_ThresholdFeedbackNode.IsNotNull() && m_NodeForThresholding.IsNotNull()) + { + if (m_ThresholdFeedbackNode->GetData()->GetTimeSteps() == 1) + { + this->UpdatePreview(); + } + } +} + template -void mitk::BinaryThresholdTool::ITKThresholding(itk::Image *originalImage, +void mitk::BinaryThresholdTool::ITKThresholding(const itk::Image *originalImage, Image *segmentation, double thresholdValue, - unsigned int timeStep) + unsigned int timeStep) const { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(thresholdValue); filter->SetUpperThreshold(m_SensibleMaximumThresholdValue); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } template -void mitk::BinaryThresholdTool::ITKThresholdingOldBinary(itk::Image *originalImage, +void mitk::BinaryThresholdTool::ITKThresholdingOldBinary(const itk::Image *originalImage, Image *segmentation, double thresholdValue, - unsigned int timeStep) + unsigned int timeStep) const { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(thresholdValue); filter->SetUpperThreshold(m_SensibleMaximumThresholdValue); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } void mitk::BinaryThresholdTool::UpdatePreview() { mitk::Image::Pointer thresholdImage = dynamic_cast(m_NodeForThresholding->GetData()); mitk::Image::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); if (thresholdImage && previewImage) { - for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) + if (previewImage->GetTimeSteps() > 1) { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(thresholdImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer feedBackImage3D = timeSelector->GetOutput(); + for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) + { + auto feedBackImage3D = this->Get3DImage(thresholdImage, timeStep); + + if (m_IsOldBinary) + { + AccessByItk_n(feedBackImage3D, ITKThresholdingOldBinary, (previewImage, m_CurrentThresholdValue, timeStep)); + } + else + { + AccessByItk_n(feedBackImage3D, ITKThresholding, (previewImage, m_CurrentThresholdValue, timeStep)); + } + } + } + else + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto feedBackImage3D = this->Get3DImageByTimePoint(thresholdImage, timePoint); + auto segTimeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); if (m_IsOldBinary) { - AccessByItk_n(feedBackImage3D, ITKThresholdingOldBinary, (previewImage, m_CurrentThresholdValue, timeStep)); + AccessByItk_n(feedBackImage3D, ITKThresholdingOldBinary, (previewImage, m_CurrentThresholdValue, segTimeStep)); } else { - AccessByItk_n(feedBackImage3D, ITKThresholding, (previewImage, m_CurrentThresholdValue, timeStep)); + AccessByItk_n(feedBackImage3D, ITKThresholding, (previewImage, m_CurrentThresholdValue, segTimeStep)); } } + RenderingManager::GetInstance()->RequestUpdateAll(); } } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h index e2a8d712e8..e0cd558f3e 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.h @@ -1,96 +1,101 @@ /*============================================================================ 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 mitkBinaryThresholdTool_h_Included #define mitkBinaryThresholdTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include #include namespace us { class ModuleResource; } namespace mitk { /** \brief Calculates the segmented volumes for binary images. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation Last contributor: $Author$ */ class MITKSEGMENTATION_EXPORT BinaryThresholdTool : public AutoSegmentationTool { public: Message3 IntervalBordersChanged; Message1 ThresholdingValueChanged; mitkClassMacro(BinaryThresholdTool, AutoSegmentationTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; void Activated() override; void Deactivated() override; virtual void SetThresholdValue(double value); virtual void AcceptCurrentThresholdValue(); virtual void CancelThresholding(); protected: BinaryThresholdTool(); // purposely hidden ~BinaryThresholdTool() override; void SetupPreviewNode(); - void CreateNewSegmentationFromThreshold(DataNode *node); + void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); + + void CreateNewSegmentationFromThreshold(); void OnRoiDataChanged(); + void OnTimePointChanged(); + void UpdatePreview(); template - void ITKThresholding(itk::Image *originalImage, + void ITKThresholding(const itk::Image *originalImage, mitk::Image *segmentation, double thresholdValue, - unsigned int timeStep); + unsigned int timeStep) const; template - void ITKThresholdingOldBinary(itk::Image *originalImage, + void ITKThresholdingOldBinary(const itk::Image *originalImage, mitk::Image *segmentation, double thresholdValue, - unsigned int timeStep); + unsigned int timeStep) const; DataNode::Pointer m_ThresholdFeedbackNode; DataNode::Pointer m_OriginalImageNode; DataNode::Pointer m_NodeForThresholding; double m_SensibleMinimumThresholdValue; double m_SensibleMaximumThresholdValue; double m_CurrentThresholdValue; bool m_IsFloatImage; bool m_IsOldBinary = false; + bool m_CreateAllTimeSteps = false; }; } // namespace #endif