diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 2bc6b9c576..bd0a9d911e 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,510 +1,463 @@ /*============================================================================ 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 "mitkRegionGrowingTool.h" #include "mitkBaseRenderer.h" #include "mitkImageToContourModelFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), - m_PaintingPixelValue(0), + m_PaintingPixelValue(1), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } -// Check whether index lies inside a segmentation -template -void mitk::RegionGrowingTool::IsInsideSegmentation(const itk::Image *itkImage, - const itk::Index& index, - bool *result) -{ - if (itkImage->GetPixel(index) > 0) - { - *result = true; - } - else - { - *result = false; - } -} - // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } -void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction*, InteractionEvent* interactionEvent) { - auto *positionEvent = dynamic_cast(interactionEvent); - if (!positionEvent) + auto* positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); - if (m_WorkingSlice.IsNotNull()) // can't do anything without a working slice (i.e. a possibly empty segmentation) + if (m_WorkingSlice.IsNull()) { - // 2. Determine if the user clicked inside or outside of the segmentation/working slice (i.e. the whole volume) - mitk::BaseGeometry::Pointer workingSliceGeometry; - workingSliceGeometry = m_WorkingSlice->GetGeometry(); - workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); - itk::Index<2> indexInWorkingSlice2D; - indexInWorkingSlice2D[0] = m_SeedPoint[0]; - indexInWorkingSlice2D[1] = m_SeedPoint[1]; - - if (workingSliceGeometry->IsIndexInside(m_SeedPoint)) - { - MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " - << m_SeedPoint << ") is inside working slice"; - - // 3. determine the pixel value under the last click to determine what to do - bool inside(true); - AccessFixedDimensionByItk_2(m_WorkingSlice, IsInsideSegmentation, 2, indexInWorkingSlice2D, &inside); - m_PaintingPixelValue = inside ? 0 : 1; - - if (inside) - { - MITK_DEBUG << "Clicked inside segmentation"; - // For now, we're doing nothing when the user clicks inside the segmentation. Behaviour can be implemented via - // OnMousePressedInside() - // When you do, be sure to remove the m_PaintingPixelValue check in OnMouseMoved() and OnMouseReleased() - return; - } - else - { - MITK_DEBUG << "Clicked outside of segmentation"; - OnMousePressedOutside(nullptr, interactionEvent); - } - } + // can't do anything without a working slice (i.e. a possibly empty segmentation) + return; } -} -// Use this to implement a behaviour for when the user clicks inside a segmentation (for example remove something) -void mitk::RegionGrowingTool::OnMousePressedInside() -{ -} + // Determine if the user clicked inside or outside of the working slice (i.e. the whole volume) + mitk::BaseGeometry::Pointer workingSliceGeometry; + workingSliceGeometry = m_WorkingSlice->GetGeometry(); + workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); + itk::Index<2> indexInWorkingSlice2D; + indexInWorkingSlice2D[0] = m_SeedPoint[0]; + indexInWorkingSlice2D[1] = m_SeedPoint[1]; -void mitk::RegionGrowingTool::OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent) -{ - auto *positionEvent = dynamic_cast(interactionEvent); + if (!workingSliceGeometry->IsIndexInside(m_SeedPoint)) + { + MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " + << m_SeedPoint << ") is not inside working slice"; + return; + } - if (positionEvent) + mitk::BaseGeometry::Pointer referenceSliceGeometry; + referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); + itk::Index<3> indexInReferenceSlice; + itk::Index<2> indexInReferenceSlice2D; + referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); + indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; + indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; + + // Get seed neighborhood + ScalarType averageValue(0); + AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); + m_SeedValue = averageValue; + MITK_DEBUG << "Seed value is " << m_SeedValue; + + // Calculate initial thresholds + AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); + m_Thresholds[0] = m_InitialThresholds[0]; + m_Thresholds[1] = m_InitialThresholds[1]; + + // Perform region growing + mitk::Image::Pointer resultImage = mitk::Image::New(); + AccessFixedDimensionByItk_3( + m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); + resultImage->SetGeometry(workingSliceGeometry); + + // Extract contour + if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { - // Get geometry and indices - mitk::BaseGeometry::Pointer workingSliceGeometry; - workingSliceGeometry = m_WorkingSlice->GetGeometry(); - itk::Index<2> indexInWorkingSlice2D; - indexInWorkingSlice2D[0] = m_SeedPoint[0]; - indexInWorkingSlice2D[1] = m_SeedPoint[1]; - - mitk::BaseGeometry::Pointer referenceSliceGeometry; - referenceSliceGeometry = - m_ReferenceSlice->GetGeometry(); - itk::Index<3> indexInReferenceSlice; - itk::Index<2> indexInReferenceSlice2D; - referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); - indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; - indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; - - // Get seed neighborhood - ScalarType averageValue(0); - AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); - m_SeedValue = averageValue; - MITK_DEBUG << "Seed value is " << m_SeedValue; - - // Calculate initial thresholds - AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); - m_Thresholds[0] = m_InitialThresholds[0]; - m_Thresholds[1] = m_InitialThresholds[1]; - - // Perform region growing - mitk::Image::Pointer resultImage = mitk::Image::New(); - AccessFixedDimensionByItk_3( - m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); - resultImage->SetGeometry(workingSliceGeometry); - - // Extract contour - if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) - { - float isoOffset = 0.33; + float isoOffset = 0.33; - mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); - contourExtractor->SetInput(resultImage); - contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); - contourExtractor->Update(); - ContourModel::Pointer resultContour = ContourModel::New(); - resultContour = contourExtractor->GetOutput(); + mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); + contourExtractor->SetInput(resultImage); + contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); + contourExtractor->Update(); + ContourModel::Pointer resultContour = ContourModel::New(); + resultContour = contourExtractor->GetOutput(); - // Show contour - if (resultContour.IsNotNull()) - { - ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( - workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); + // Show contour + if (resultContour.IsNotNull()) + { + ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( + workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); - FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); + FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); - FeedbackContourTool::SetFeedbackContourVisible(true); - mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); - } + FeedbackContourTool::SetFeedbackContourVisible(true); + mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } -void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction*, InteractionEvent* interactionEvent) { - // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, - // i.e. when the user clicked inside the segmentation - if (m_PaintingPixelValue == 0) + auto* positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { + return; + } + + if (m_ReferenceSlice.IsNull()) { return; } - auto *positionEvent = dynamic_cast(interactionEvent); + // Get geometry and indices + mitk::BaseGeometry::Pointer workingSliceGeometry; + workingSliceGeometry = m_WorkingSlice->GetGeometry(); + itk::Index<2> indexInWorkingSlice2D; + indexInWorkingSlice2D[0] = m_SeedPoint[0]; + indexInWorkingSlice2D[1] = m_SeedPoint[1]; + + m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; + m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; + m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); - if (m_ReferenceSlice.IsNotNull() && positionEvent) + // Moving the mouse up and down adjusts the width of the threshold window, + // moving it left and right shifts the threshold window + m_Thresholds[0] = std::min( + m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); + m_Thresholds[1] = std::max( + m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); + + // Do not exceed the pixel type extrema of the reference slice, though + m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); + m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); + + // Perform region growing again and show the result + mitk::Image::Pointer resultImage = mitk::Image::New(); + AccessFixedDimensionByItk_3( + m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); + resultImage->SetGeometry(workingSliceGeometry); + + // Update the contour + if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { - // Get geometry and indices - mitk::BaseGeometry::Pointer workingSliceGeometry; - workingSliceGeometry = m_WorkingSlice->GetGeometry(); - itk::Index<2> indexInWorkingSlice2D; - indexInWorkingSlice2D[0] = m_SeedPoint[0]; - indexInWorkingSlice2D[1] = m_SeedPoint[1]; - - m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; - m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; - m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); - - // Moving the mouse up and down adjusts the width of the threshold window, - // moving it left and right shifts the threshold window - m_Thresholds[0] = std::min(m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); - m_Thresholds[1] = std::max(m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); - - // Do not exceed the pixel type extrema of the reference slice, though - m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); - m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); - - // Perform region growing again and show the result - mitk::Image::Pointer resultImage = mitk::Image::New(); - AccessFixedDimensionByItk_3( - m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); - resultImage->SetGeometry(workingSliceGeometry); - - // Update the contour - if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) - { - float isoOffset = 0.33; + float isoOffset = 0.33; - mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); - contourExtractor->SetInput(resultImage); - contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); - contourExtractor->Update(); - ContourModel::Pointer resultContour = ContourModel::New(); - resultContour = contourExtractor->GetOutput(); + mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); + contourExtractor->SetInput(resultImage); + contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); + contourExtractor->Update(); + ContourModel::Pointer resultContour = ContourModel::New(); + resultContour = contourExtractor->GetOutput(); - // Show contour - if (resultContour.IsNotNull()) - { - ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( - workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); + // Show contour + if (resultContour.IsNotNull()) + { + ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( + workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); - FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); + FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); - FeedbackContourTool::SetFeedbackContourVisible(true); - mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); - } + FeedbackContourTool::SetFeedbackContourVisible(true); + mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } -void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) +void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction*, InteractionEvent* interactionEvent) { - // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, - // i.e. when the user clicked inside the segmentation - if (m_PaintingPixelValue == 0) + auto* positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) { return; } - auto *positionEvent = dynamic_cast(interactionEvent); + if (m_WorkingSlice.IsNull() && m_FillFeedbackContour) + { + return; + } - if (m_WorkingSlice.IsNotNull() && m_FillFeedbackContour && positionEvent) + if (m_FillFeedbackContour) { this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.h b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.h index 6ba9999718..314c52cd89 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.h +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.h @@ -1,160 +1,138 @@ /*============================================================================ 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 mitkRegionGrowingTool_h_Included #define mitkRegionGrowingTool_h_Included #include "mitkFeedbackContourTool.h" #include #include namespace us { class ModuleResource; } namespace mitk { /** \brief A slice based region growing tool. \sa FeedbackContourTool \ingroup Interaction \ingroup ToolManagerEtAl When the user presses the mouse button, RegionGrowingTool will use the gray values at that position to initialize a region growing algorithm (in the affected 2D slice). By moving the mouse up and down while the button is still pressed, the user can widen or narrow the threshold window, i.e. select more or less within the desired region. The current result of region growing will always be shown as a contour to the user. After releasing the button, the current result of the region growing algorithm will be written to the working image of this tool's ToolManager. - If the first click is inside a segmentation, nothing will happen (other behaviour, for example removal of a - region, can be implemented via OnMousePressedInside()). - \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT RegionGrowingTool : public FeedbackContourTool { public: mitkClassMacro(RegionGrowingTool, FeedbackContourTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: RegionGrowingTool(); // purposely hidden ~RegionGrowingTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; /** * @brief OnMousePressed is called when the user clicks. - * Calls either OnMousePressedInside() or OnMousePressedOutside(). + * Grows a region by calling 'GetNeighborhoodAverage' and 'StartRegionGrowing'. */ virtual void OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent); - /** - * @brief OnMousePressedInside can be used to implement behaviour when the user clicks inside a segmentation. - */ - virtual void OnMousePressedInside(); - - /** - * @brief OnMousePressedOutside is called when the user clicks outside of the segmented area. - * Grows a region. - */ - virtual void OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent); - /** * @brief OnMouseMoved is called when the user moves the mouse with the left mouse button pressed. * Adjusts the thresholds. * Up: Increase upper threshold, decrease lower threshold. * Down: Decrease upper threshold, increase lower threshold. * Right: Increase both thresholds. * Left: Decrease both thresholds. */ virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /** * @brief OnMouseReleased converts the feedback contour to a segmentation. */ virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent); /** * @brief Template to calculate average pixel value around index using a square/cube with radius neighborhood. * Example: 1 = 3x3 pixels, 2 = 5x5 pixels, etc. */ template void GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood = 1); - /** - * @brief Template to check whether index is inside already segmented area. - */ - template - void IsInsideSegmentation(const itk::Image *itkImage, - const itk::Index& index, - bool *result); - /** * @brief Template that calls an ITK filter to do the region growing. */ template void StartRegionGrowing(const itk::Image *itkImage, const itk::Index& seedPoint, const std::array& thresholds, mitk::Image::Pointer &outputImage); /** * @brief Template to calculate the initial thresholds for region growing. */ template void CalculateInitialThresholds(const itk::Image* itkImage); Image::Pointer m_ReferenceSlice; Image::Pointer m_WorkingSlice; ScalarType m_SeedValue; itk::Index<3> m_SeedPoint; std::array m_ThresholdExtrema; std::array m_Thresholds; std::array m_InitialThresholds; Point2I m_LastScreenPosition; int m_ScreenYDifference; int m_ScreenXDifference; private: ScalarType m_MouseDistanceScaleFactor; int m_PaintingPixelValue; bool m_FillFeedbackContour; int m_ConnectedComponentValue; }; } // namespace #endif diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/Manual.dox index c90a30a4de..8e7b2a3538 100644 --- a/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/Manual.dox @@ -1,16 +1,70 @@ /** \page org_mitk_views_fit_genericfitting The Model Fit Generic Fitting View \imageMacro{fit_generic_doc.svg,"Icon of the Generic Fitting View",3.0} \tableofcontents \section FIT_GENERIC_Introduction Introduction -This plug-in offers a generic fitting component for time resolved image data. +This views offers a generic fitting component for time resolved image data. + +\section FIT_GENERIC__Contact Contact information +If you have any questions, need support, find a bug or have a feature request, feel free to contact us at www.mitk.org. + +\subsection FIT_GENERIC__Cite Citation information +If you use the view for your research please cite our work as reference:\n\n +Debus C and Floca R, Ingrisch M, Kompan I, Maier-Hein K, Abdollahi A, Nolden M, MITK-ModelFit: generic open-source framework for model fits and their exploration in medical imaging – design, implementation and application on the example of DCE-MRI. https://doi.org/10.1186/s12859-018-2588-1 (BMC Bioinformatics 2019 20:31) + + +\section FIT_GENERIC__Data_and_ROI_Selection Time series and mask selection +\imageMacro{modelfit_generic_maskAndFittingStrategy.png, "Time series and mask selection.", 10} +In principle, every model can be fitted on the entire image. However, for model configuration reasons and computational time cost, this is often not advisable. +Therefore, apart from the image to be fitted (Selected Time Series), a ROI segmentation can be defined (Selected Mask), within which model fitting is performed. +The view currently offers Pixel based and/or ROI based averaged fits of time-varying curves. The ROI based fitting option becomes enabled, if a mask is selected. + +\section FIT_GENERIC_General_models Supported models +Currently the following models are available for fitting: +- Linear model : y = offset + slope*x +- Generic parameter model : A simple formula can be manually provided and will be parsed for fitting. +- T2 decay model : y = A*exp(-t/B) + + +\section FIT_GENERIC_Settings Model settings + +\subsection FIT_DCE_Settings_model Model specific settings +Selecting one of the \ref FIT_GENERIC_General_models "supported models" will open below tabs for further configuration of the model. +- The Generic parameter model required as input a formula to be parsed and the number parameters to fit. In the following image, detailed formula information is given. +\imageMacro{modelfit_generic_genericParamModelSettings.png, "Formula info for the generic parameter model", 10} + +\subsection FIT_GENERIC_Settings_start Start parameter +In cases of noisy data it can be useful to define the initial starting values of the parameter estimates, at which optimization starts, in order to prevent optimization results in local optima. +Each model has default scalar values (applied to every voxel) for initial values of each parameter, however these can be adjusted. +Moreover, initial values can also be defined locally for each individual voxel via starting value images. To load a starting value image, change the Type from scalar to image. This can be done by double-clicking on the type cell. +In the Value column, selection of a starting value image will be available. + +\subsection FIT_GENERIC_Settings_constraint Constraints settings +To limit the fitting search space and to exclude unphysical/illogical results for model parameter estimates, constraints to individual parameters as well as combinations can be imposed. +Each model has default constraints, however, new ones can be defined or removed by the + and – buttons in the table. +The first column specifies the parameter(s) involved in the constraint (if multiple parameters are selected, their sum will be used) by selection in the drop down menu. +The second column Type defines whether the constraint defines an upper or lower boundary. +Value defines the actual constraint value, that should not be crossed, and Width allows for a certain tolerance width. + + +\section FIT_GENERIC_Fitting Executing a fit + +In order to distinguish results from different model fits to the data, a Fitting name can be defined. +As default, the name of the model and the fitting strategy (pixel/ROI) are given. This name will then be appended by the respective parameter name.\n\n + +For development purposes and evaluation of the fits, the option Generate debug parameter images is available. +Enabling this option will result in additional parameter maps displaying the status of the optimizer at fit termination. +In the following definitions, an evaluation describes the process of cost function calculation and evaluation by the optimizer for a given parameter set. + +- Stop condition: Reasons for the fit termination, i.e. criterion reached, maximum number of iterations,... +- Optimization time: The overall time from fitting start to termination. +- Number of iterations: The number of iterations from fitting start to termination. + +After all necessary configurations are set, the button Start Modelling is enabled, which starts the fitting routine. +Progress can be seen in the message box on the bottom. Resulting parameter maps will afterwards be added to the Data Manager as sub-nodes of the analyzed 4D image. -\section FIT_GENERIC_Contact Contact information -This plug-in is being developed by the SIDT group (Software development for Integrated Diagnostics -and Therapy) at the German Cancer Research Center (DKFZ). If you have any questions, need support, -find a bug or have a feature request, feel free to contact us at www.mitk.org. */ diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_genericParamModelSettings.png b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_genericParamModelSettings.png new file mode 100644 index 0000000000..c222af2009 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_genericParamModelSettings.png differ diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_maskAndFittingStrategy.png b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_maskAndFittingStrategy.png new file mode 100644 index 0000000000..497c3257ce Binary files /dev/null and b/Plugins/org.mitk.gui.qt.fit.genericfitting/documentation/UserManual/modelfit_generic_maskAndFittingStrategy.png differ