diff --git a/Modules/Segmentation/Interactions/mitkContourTool.cpp b/Modules/Segmentation/Interactions/mitkContourTool.cpp index d1883b96c9..a4791bc212 100644 --- a/Modules/Segmentation/Interactions/mitkContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkContourTool.cpp @@ -1,157 +1,166 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkContourTool.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" //#include "mitkProperties.h" #include "mitkPlanarCircle.h" mitk::ContourTool::ContourTool(int paintingPixelValue) :FeedbackContourTool("PressMoveReleaseWithCTRLInversion"), m_PaintingPixelValue(paintingPixelValue) { + // great magic numbers + CONNECT_ACTION( 80, OnMousePressed ); + CONNECT_ACTION( 90, OnMouseMoved ); + CONNECT_ACTION( 42, OnMouseReleased ); + CONNECT_ACTION( 49014, OnInvertLogic ); } mitk::ContourTool::~ContourTool() { } void mitk::ContourTool::Activated() { Superclass::Activated(); } void mitk::ContourTool::Deactivated() { Superclass::Deactivated(); } /** Just show the contour, insert the first point. */ bool mitk::ContourTool::OnMousePressed (Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnMousePressed( action, stateEvent )) return false; - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; + + Contour* contour = FeedbackContourTool::GetFeedbackContour(); contour->Initialize(); contour->AddVertex( positionEvent->GetWorldPosition() ); FeedbackContourTool::SetFeedbackContourVisible(true); assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } /** Insert the point to the feedback contour. */ bool mitk::ContourTool::OnMouseMoved (Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnMouseMoved( action, stateEvent )) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; Contour* contour = FeedbackContourTool::GetFeedbackContour(); contour->AddVertex( positionEvent->GetWorldPosition() ); assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } /** Close the contour, project it to the image slice and fill it in 2D. */ bool mitk::ContourTool::OnMouseReleased(Action* action, const StateEvent* stateEvent) { // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's working image corresponds to that FeedbackContourTool::SetFeedbackContourVisible(false); const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); - if (!FeedbackContourTool::OnMouseReleased( action, stateEvent )) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if (!workingNode) return false; Image* image = dynamic_cast(workingNode->GetData()); const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); if ( !image || !planeGeometry ) return false; // 2. Slice is known, now we try to get it as a 2D image and project the contour into index coordinates of this slice Image::Pointer slice = SegTool2D::GetAffectedImageSliceAs2DImage( positionEvent, image ); if ( slice.IsNull() ) { MITK_ERROR << "Unable to extract slice." << std::endl; return false; } Contour* feedbackContour( FeedbackContourTool::GetFeedbackContour() ); Contour::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( slice, feedbackContour, true, false ); // true: actually no idea why this is neccessary, but it works :-( if (projectedContour.IsNull()) return false; FeedbackContourTool::FillContourInSlice( projectedContour, slice, m_PaintingPixelValue ); this->WriteBackSegmentationResult(positionEvent, slice); // 4. Make sure the result is drawn again --> is visible then. assert( positionEvent->GetSender()->GetRenderWindow() ); return true; } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ bool mitk::ContourTool::OnInvertLogic(Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnInvertLogic(action, stateEvent)) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor( 1.0, 0.0, 0.0 ); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } return true; } diff --git a/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp b/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp index ba8882ed1c..8dff50295e 100644 --- a/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp @@ -1,145 +1,153 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkCorrectorTool2D.h" #include "mitkCorrectorAlgorithm.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkCorrectorTool2D.xpm" #include "mitkOverwriteDirectedPlaneImageFilter.h" namespace mitk { MITK_TOOL_MACRO(Segmentation_EXPORT, CorrectorTool2D, "Correction tool"); } mitk::CorrectorTool2D::CorrectorTool2D(int paintingPixelValue) :FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) { + // great magic numbers + CONNECT_ACTION( 80, OnMousePressed ); + CONNECT_ACTION( 90, OnMouseMoved ); + CONNECT_ACTION( 42, OnMouseReleased ); + GetFeedbackContour()->SetClosed( false ); // don't close the contour to a polygon } mitk::CorrectorTool2D::~CorrectorTool2D() { } const char** mitk::CorrectorTool2D::GetXPM() const { return mitkCorrectorTool2D_xpm; } const char* mitk::CorrectorTool2D::GetName() const { return "Correction"; } void mitk::CorrectorTool2D::Activated() { Superclass::Activated(); } void mitk::CorrectorTool2D::Deactivated() { Superclass::Deactivated(); } bool mitk::CorrectorTool2D::OnMousePressed (Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnMousePressed( action, stateEvent )) return false; - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; + Contour* contour = FeedbackContourTool::GetFeedbackContour(); contour->Initialize(); contour->AddVertex( positionEvent->GetWorldPosition() ); FeedbackContourTool::SetFeedbackContourVisible(true); return true; } bool mitk::CorrectorTool2D::OnMouseMoved (Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnMouseMoved( action, stateEvent )) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; Contour* contour = FeedbackContourTool::GetFeedbackContour(); contour->AddVertex( positionEvent->GetWorldPosition() ); assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } bool mitk::CorrectorTool2D::OnMouseReleased(Action* action, const StateEvent* stateEvent) { // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's working image corresponds to that FeedbackContourTool::SetFeedbackContourVisible(false); const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); - if (!FeedbackContourTool::OnMouseReleased( action, stateEvent )) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if (!workingNode) return false; Image* image = dynamic_cast(workingNode->GetData()); const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); if ( !image || !planeGeometry ) return false; // 2. Slice is known, now we try to get it as a 2D image and project the contour into index coordinates of this slice m_WorkingSlice = FeedbackContourTool::GetAffectedImageSliceAs2DImage( positionEvent, image ); if ( m_WorkingSlice.IsNull() ) { MITK_ERROR << "Unable to extract slice." << std::endl; return false; } CorrectorAlgorithm::Pointer algorithm = CorrectorAlgorithm::New(); algorithm->SetInput( m_WorkingSlice ); algorithm->SetContour( FeedbackContourTool::GetFeedbackContour() ); try { algorithm->UpdateLargestPossibleRegion(); } catch ( std::exception& e ) { MITK_ERROR << "Caught exception '" << e.what() << "'" << std::endl; } mitk::Image::Pointer resultSlice = mitk::Image::New(); resultSlice->Initialize(algorithm->GetOutput()); resultSlice->SetVolume(algorithm->GetOutput()->GetData()); this->WriteBackSegmentationResult(positionEvent, resultSlice); return true; } diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp index 23775aceb5..3f4e6f8ea0 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp @@ -1,377 +1,389 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkPaintbrushTool.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkBaseRenderer.h" #include "mitkImageDataItem.h" #include "ipSegmentation.h" #include "mitkLevelWindowProperty.h" #define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) int mitk::PaintbrushTool::m_Size = 1; mitk::PaintbrushTool::PaintbrushTool(int paintingPixelValue) :FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"), m_PaintingPixelValue(paintingPixelValue), m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28) { + // great magic numbers + CONNECT_ACTION( 80, OnMousePressed ); + CONNECT_ACTION( 90, OnMouseMoved ); + CONNECT_ACTION( 42, OnMouseReleased ); + CONNECT_ACTION( 49014, OnInvertLogic ); + + m_MasterContour = Contour::New(); m_MasterContour->Initialize(); m_CurrentPlane = NULL; m_WorkingNode = DataNode::New(); m_WorkingNode->SetProperty( "levelwindow", mitk::LevelWindowProperty::New( mitk::LevelWindow(0, 1) ) ); m_WorkingNode->SetProperty( "binary", mitk::BoolProperty::New(true) ); } mitk::PaintbrushTool::~PaintbrushTool() { } void mitk::PaintbrushTool::Activated() { Superclass::Activated(); FeedbackContourTool::SetFeedbackContourVisible(true); SizeChanged.Send(m_Size); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); if (m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); Superclass::Deactivated(); m_WorkingSlice = NULL; m_CurrentPlane = NULL; } void mitk::PaintbrushTool::SetSize(int value) { m_Size = value; } void mitk::PaintbrushTool::UpdateContour(const StateEvent* stateEvent) { //MITK_INFO<<"Update..."; // examine stateEvent and create a contour that matches the pixel mask that we are going to draw const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; // create a copy of this slice (at least match the pixel sizes/spacings), // then draw the desired mask on it and create a contour from it // Convert to ipMITKSegmentationTYPE (because getting pixels relys on that data type) itk::Image< ipMITKSegmentationTYPE, 2 >::Pointer correctPixelTypeImage; CastToItkImage(m_WorkingSlice/*dynamic_cast(m_WorkingNode->GetData())*/, correctPixelTypeImage ); assert (correctPixelTypeImage.IsNotNull() ); itk::Image< ipMITKSegmentationTYPE, 2 >::DirectionType imageDirection; imageDirection.SetIdentity(); correctPixelTypeImage->SetDirection(imageDirection); Image::Pointer temporarySlice = Image::New(); CastToMitkImage( correctPixelTypeImage, temporarySlice ); //mitkIpPicDescriptor* stupidClone = mitkIpPicClone( temporarySlice->GetSliceData()->GetPicDescriptor() ); mitkIpPicDescriptor* stupidClone = mitkIpPicNew(); CastToIpPicDescriptor( temporarySlice->GetSliceData(), stupidClone ); unsigned int pixelWidth = m_Size + 1; unsigned int pixelHeight = m_Size + 1; if ( stupidClone->n[0] <= pixelWidth || stupidClone->n[1] <= pixelHeight ) { MITK_INFO << "Brush size is bigger than your working image. Reconsider this...\n" "(Or tell your progammer until (s)he fixes this message.)" << std::endl; mitkIpPicFree( stupidClone ); return; } unsigned int lineLength( stupidClone->n[0] ); unsigned int oneContourOffset(0); float circleCenterX = (float)m_Size / 2.0; float circleCenterY = (float)m_Size / 2.0; for (unsigned int x = 0; x <= pixelWidth; ++x) { for (unsigned int y = 0; y <= pixelHeight; ++y) { unsigned int offset = lineLength * y + x; ipMITKSegmentationTYPE* current = (ipMITKSegmentationTYPE*)stupidClone->data + offset; float pixelCenterX = x + 0.5; float pixelCenterY = y + 0.5; float xoff = pixelCenterX - circleCenterX; float yoff = pixelCenterY - circleCenterY; bool inside = xoff * xoff + yoff * yoff < (m_Size * m_Size) / 4.0; // no idea, if this would work for ellipses if (inside) { *current = 1; oneContourOffset = offset; } else { *current = 0; } } } int numberOfContourPoints( 0 ); int newBufferSize( 0 ); float* contourPoints = ipMITKSegmentationGetContour8N( stupidClone, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc if (!contourPoints) { mitkIpPicFree( stupidClone ); return; } // copy point from float* to mitk::Contour Contour::Pointer contourInImageIndexCoordinates = Contour::New(); contourInImageIndexCoordinates->Initialize(); Point3D newPoint; //ipMITKSegmentationGetContour8N returns all points, which causes vtk warnings, since the first and the last points are coincident. //leaving the last point out, the contour is still drawn correctly for (int index = 0; index < numberOfContourPoints-1; ++index) { newPoint[0] = contourPoints[ 2 * index + 0 ] - circleCenterX; // master contour should be centered around (0,0) newPoint[1] = contourPoints[ 2 * index + 1] - circleCenterY; newPoint[2] = 0.0; MITK_DEBUG << "Point [" << index << "] (" << newPoint[0] << ", " << newPoint[1] << ")" << std::endl; contourInImageIndexCoordinates->AddVertex( newPoint ); } free(contourPoints); m_MasterContour = contourInImageIndexCoordinates; // The PicDescriptor is only REFERENCING(!) the data, the temporarySlice takes care of deleting the data also the descriptor is pointing on // because they got allocated by the ImageDataItem, not the descriptor. stupidClone->data = NULL; mitkIpPicFree( stupidClone ); } /** Just show the contour, get one point as the central point and add surrounding points to the contour. */ bool mitk::PaintbrushTool::OnMousePressed (Action* action, const StateEvent* stateEvent) { + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + return this->OnMouseMoved(action, stateEvent); } /** Insert the point to the feedback contour,finish to build the contour and at the same time the painting function */ bool mitk::PaintbrushTool::OnMouseMoved (Action* itkNotUsed(action), const StateEvent* stateEvent) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); - if (!positionEvent) return false; - CheckIfCurrentSliceHasChanged(positionEvent); + CheckIfCurrentSliceHasChanged(positionEvent); - if ( m_LastContourSize != m_Size ) - { - UpdateContour( stateEvent ); - m_LastContourSize = m_Size; - } + if ( m_LastContourSize != m_Size ) + { + UpdateContour( stateEvent ); + m_LastContourSize = m_Size; + } bool leftMouseButtonPressed( - stateEvent->GetId() == 530 - || stateEvent->GetId() == 534 - || stateEvent->GetId() == 1 - || stateEvent->GetId() == 5 - ); - + stateEvent->GetId() == 530 + || stateEvent->GetId() == 534 + || stateEvent->GetId() == 1 + || stateEvent->GetId() == 5 + ); + Point3D worldCoordinates = positionEvent->GetWorldPosition(); Point3D indexCoordinates; m_WorkingSlice->GetGeometry()->WorldToIndex( worldCoordinates, indexCoordinates ); MITK_DEBUG << "Mouse at W " << worldCoordinates << std::endl; MITK_DEBUG << "Mouse at I " << indexCoordinates << std::endl; // round to nearest voxel center (abort if this hasn't changed) if ( m_Size % 2 == 0 ) // even { indexCoordinates[0] = ROUND( indexCoordinates[0] /*+ 0.5*/) + 0.5; indexCoordinates[1] = ROUND( indexCoordinates[1] /*+ 0.5*/ ) + 0.5; } else // odd { indexCoordinates[0] = ROUND( indexCoordinates[0] ) ; indexCoordinates[1] = ROUND( indexCoordinates[1] ) ; } static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me if ( fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps || fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed ) { lastPos = indexCoordinates; } else { MITK_DEBUG << "." << std::flush; return false; } MITK_DEBUG << "Mouse at C " << indexCoordinates; Contour::Pointer contour = Contour::New(); contour->Initialize(); for (unsigned int index = 0; index < m_MasterContour->GetNumberOfPoints(); ++index) { Point3D point = m_MasterContour->GetPoints()->ElementAt(index); point[0] += indexCoordinates[ 0 ]; point[1] += indexCoordinates[ 1 ]; contour->AddVertex( point ); } if (leftMouseButtonPressed) { FeedbackContourTool::FillContourInSlice( contour, m_WorkingSlice, m_PaintingPixelValue ); m_WorkingNode->SetData(m_WorkingSlice); m_WorkingNode->Modified(); } // visualize contour Contour::Pointer displayContour = Contour::New(); displayContour->Initialize(); //for (unsigned int index = 0; index < contour->GetNumberOfPoints(); ++index) //{ // Point3D point = contour->GetPoints()->ElementAt(index); // if ( m_Size % 2 == 0 ) // even // { // point[0] += 0.5; // point[1] += 0.5; // } // displayContour->AddVertex( point ); //} displayContour = FeedbackContourTool::BackProjectContourFrom2DSlice( m_WorkingSlice->GetGeometry(), /*displayContour*/contour ); SetFeedbackContour( *displayContour ); assert( positionEvent->GetSender()->GetRenderWindow() ); RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } bool mitk::PaintbrushTool::OnMouseReleased(Action* /*action*/, const StateEvent* stateEvent) { //When mouse is released write segmentationresult back into image const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone()); return true; } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ bool mitk::PaintbrushTool::OnInvertLogic(Action* itkNotUsed(action), const StateEvent* stateEvent) { // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor( 1.0, 0.0, 0.0 ); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return true; } void mitk::PaintbrushTool::CheckIfCurrentSliceHasChanged(const PositionEvent *event) { const PlaneGeometry* planeGeometry( dynamic_cast (event->GetSender()->GetCurrentWorldGeometry2D() ) ); DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if (!workingNode) return; Image::Pointer image = dynamic_cast(workingNode->GetData()); if ( !image || !planeGeometry ) return; if(m_CurrentPlane.IsNull()) { m_CurrentPlane = const_cast(planeGeometry); m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode->ReplaceProperty( "color", workingNode->GetProperty("color") ); m_WorkingNode->SetData(m_WorkingSlice); } else { bool isSameSlice (false); isSameSlice = mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(),m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix()); isSameSlice = mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(),m_CurrentPlane->GetIndexToWorldTransform()->GetOffset()); if (!isSameSlice) { m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); m_CurrentPlane = NULL; m_WorkingSlice = NULL; m_WorkingNode = NULL; m_CurrentPlane = const_cast(planeGeometry); m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode = mitk::DataNode::New(); m_WorkingNode->SetProperty( "levelwindow", mitk::LevelWindowProperty::New( mitk::LevelWindow(0, 1) ) ); m_WorkingNode->SetProperty( "binary", mitk::BoolProperty::New(true) ); m_WorkingNode->SetData(m_WorkingSlice); //So that the paintbrush contour vanished in the previous render window RenderingManager::GetInstance()->RequestUpdateAll(); } } if(!m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) { m_WorkingNode->SetProperty( "outline binary", mitk::BoolProperty::New(true) ); m_WorkingNode->SetProperty( "color", workingNode->GetProperty("color") ); m_WorkingNode->SetProperty( "name", mitk::StringProperty::New("Paintbrush_Node") ); m_WorkingNode->SetProperty( "helper object", mitk::BoolProperty::New(true) ); m_WorkingNode->SetProperty( "opacity", mitk::FloatProperty::New(0.8) ); m_WorkingNode->SetProperty( "includeInBoundingBox", mitk::BoolProperty::New(false)); m_WorkingNode->SetVisibility(false, mitk::BaseRenderer::GetInstance( mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget4"))); m_ToolManager->GetDataStorage()->Add(m_WorkingNode); } } diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 8e4ffbbd1e..f2f485e13e 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,660 +1,670 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkRegionGrowingTool.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkImageDataItem.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkApplicationCursor.h" #include "ipSegmentation.h" #include "mitkRegionGrowingTool.xpm" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkExtractDirectedPlaneImageFilterNew.h" namespace mitk { MITK_TOOL_MACRO(Segmentation_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) mitk::RegionGrowingTool::RegionGrowingTool() :FeedbackContourTool("PressMoveRelease"), m_LowerThreshold(200), m_UpperThreshold(200), m_InitialLowerThreshold(200), m_InitialUpperThreshold(200), m_ScreenYDifference(0), m_OriginalPicSlice(NULL), m_SeedPointMemoryOffset(0), m_VisibleWindow(0), m_DefaultWindow(0), m_MouseDistanceScaleFactor(0.5), m_LastWorkingSeed(-1), m_FillFeedbackContour(true) { + // great magic numbers + CONNECT_ACTION( 80, OnMousePressed ); + CONNECT_ACTION( 90, OnMouseMoved ); + CONNECT_ACTION( 42, OnMouseReleased ); + } mitk::RegionGrowingTool::~RegionGrowingTool() { } const char** mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } const char* mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } /** 1 Determine which slice is clicked into 2 Determine if the user clicked inside or outside of the segmentation 3 Depending on the pixel value under the mouse click position, two different things happen: (separated out into OnMousePressedInside and OnMousePressedOutside) 3.1 Create a skeletonization of the segmentation and try to find a nice cut 3.1.1 Call a ipSegmentation algorithm to create a nice cut 3.1.2 Set the result of this algorithm as the feedback contour 3.2 Initialize region growing 3.2.1 Determine memory offset inside the original image 3.2.2 Determine initial region growing parameters from the level window settings of the image 3.2.3 Perform a region growing (which generates a new feedback contour) */ bool mitk::RegionGrowingTool::OnMousePressed (Action* action, const StateEvent* stateEvent) { + const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); + if (!positionEvent) return false; + + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + //ToolLogger::SetVerboseness(3); MITK_DEBUG << "OnMousePressed" << std::endl; - if (FeedbackContourTool::OnMousePressed( action, stateEvent )) + if ( FeedbackContourTool::CanHandleEvent(stateEvent) > 0.0 ) { MITK_DEBUG << "OnMousePressed: FeedbackContourTool says ok" << std::endl; // 1. Find out which slice the user clicked, find out which slice of the toolmanager's reference and working image corresponds to that - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (positionEvent) { MITK_DEBUG << "OnMousePressed: got positionEvent" << std::endl; m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice( positionEvent ); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice( positionEvent ); if ( m_WorkingSlice.IsNotNull() ) // can't do anything without the segmentation { MITK_DEBUG << "OnMousePressed: got working slice" << std::endl; // 2. Determine if the user clicked inside or outside of the segmentation const Geometry3D* workingSliceGeometry = m_WorkingSlice->GetGeometry(); Point3D mprojectedPointIn2D; workingSliceGeometry->WorldToIndex( positionEvent->GetWorldPosition(), mprojectedPointIn2D); itk::Index<2> projectedPointInWorkingSlice2D; projectedPointInWorkingSlice2D[0] = static_cast( mprojectedPointIn2D[0] - 0.5 ); projectedPointInWorkingSlice2D[1] = static_cast( mprojectedPointIn2D[1] - 0.5 ); if ( workingSliceGeometry->IsIndexInside( projectedPointInWorkingSlice2D ) ) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetWorldPosition() << " (index coordinates " << projectedPointInWorkingSlice2D << ") IS in working slice" << std::endl; // Convert to ipMITKSegmentationTYPE (because getting pixels relys on that data type) itk::Image< ipMITKSegmentationTYPE, 2 >::Pointer correctPixelTypeImage; CastToItkImage( m_WorkingSlice, correctPixelTypeImage ); assert (correctPixelTypeImage.IsNotNull() ); // possible bug in CastToItkImage ? // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479: // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 && aSpacing[2]>0' failed // solution here: we overwrite it with an unity matrix itk::Image< ipMITKSegmentationTYPE, 2 >::DirectionType imageDirection; imageDirection.SetIdentity(); correctPixelTypeImage->SetDirection(imageDirection); Image::Pointer temporarySlice = Image::New(); // temporarySlice = ImportItkImage( correctPixelTypeImage ); CastToMitkImage( correctPixelTypeImage, temporarySlice ); mitkIpPicDescriptor* workingPicSlice = mitkIpPicNew(); CastToIpPicDescriptor(temporarySlice, workingPicSlice); int initialWorkingOffset = projectedPointInWorkingSlice2D[1] * workingPicSlice->n[0] + projectedPointInWorkingSlice2D[0]; if ( initialWorkingOffset < static_cast( workingPicSlice->n[0] * workingPicSlice->n[1] ) && initialWorkingOffset >= 0 ) { // 3. determine the pixel value under the last click bool inside = static_cast(workingPicSlice->data)[initialWorkingOffset] != 0; m_PaintingPixelValue = inside ? 0 : 1; // if inside, we want to remove a part, otherwise we want to add something if ( m_LastWorkingSeed >= static_cast( workingPicSlice->n[0] * workingPicSlice->n[1] ) || m_LastWorkingSeed < 0 ) { inside = false; } if ( m_ReferenceSlice.IsNotNull() ) { MITK_DEBUG << "OnMousePressed: got reference slice" << std::endl; m_OriginalPicSlice = mitkIpPicNew(); CastToIpPicDescriptor(m_ReferenceSlice, m_OriginalPicSlice); // 3.1. Switch depending on the pixel value if (inside) { OnMousePressedInside(action, stateEvent, workingPicSlice, initialWorkingOffset); } else { OnMousePressedOutside(action, stateEvent); } } } } } } } MITK_DEBUG << "end OnMousePressed" << std::endl; return true; } /** 3.1 Create a skeletonization of the segmentation and try to find a nice cut 3.1.1 Call a ipSegmentation algorithm to create a nice cut 3.1.2 Set the result of this algorithm as the feedback contour */ bool mitk::RegionGrowingTool::OnMousePressedInside(Action* itkNotUsed( action ), const StateEvent* stateEvent, mitkIpPicDescriptor* workingPicSlice, int initialWorkingOffset) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in OnMousePressed // 3.1.1. Create a skeletonization of the segmentation and try to find a nice cut // apply the skeletonization-and-cut algorithm // generate contour to remove // set m_ReferenceSlice = NULL so nothing will happen during mouse move // remember to fill the contour with 0 in mouserelease mitkIpPicDescriptor* segmentationHistory = ipMITKSegmentationCreateGrowerHistory( workingPicSlice, m_LastWorkingSeed, NULL ); // free again if (segmentationHistory) { tCutResult cutContour = ipMITKSegmentationGetCutPoints( workingPicSlice, segmentationHistory, initialWorkingOffset ); // tCutResult is a ipSegmentation type mitkIpPicFree( segmentationHistory ); if (cutContour.cutIt) { // 3.1.2 copy point from float* to mitk::Contour Contour::Pointer contourInImageIndexCoordinates = Contour::New(); contourInImageIndexCoordinates->Initialize(); Point3D newPoint; for (int index = 0; index < cutContour.deleteSize; ++index) { newPoint[0] = cutContour.deleteCurve[ 2 * index + 0 ]; newPoint[1] = cutContour.deleteCurve[ 2 * index + 1 ]; newPoint[2] = 0.0; contourInImageIndexCoordinates->AddVertex( newPoint - 0.5 ); } free(cutContour.traceline); free(cutContour.deleteCurve); // perhaps visualize this for fun? free(cutContour.onGradient); Contour::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( m_WorkingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for ipSegmentation correction FeedbackContourTool::SetFeedbackContour( *contourInWorldCoordinates ); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); m_FillFeedbackContour = true; } else { m_FillFeedbackContour = false; } } else { m_FillFeedbackContour = false; } m_ReferenceSlice = NULL; return true; } /** 3.2 Initialize region growing 3.2.1 Determine memory offset inside the original image 3.2.2 Determine initial region growing parameters from the level window settings of the image 3.2.3 Perform a region growing (which generates a new feedback contour) */ bool mitk::RegionGrowingTool::OnMousePressedOutside(Action* itkNotUsed( action ), const StateEvent* stateEvent) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in OnMousePressed // 3.2 If we have a reference image, then perform an initial region growing, considering the reference image's level window // if click was outside the image, don't continue const Geometry3D* sliceGeometry = m_ReferenceSlice->GetGeometry(); Point3D mprojectedPointIn2D; sliceGeometry->WorldToIndex( positionEvent->GetWorldPosition(), mprojectedPointIn2D ); itk::Index<2> projectedPointIn2D; projectedPointIn2D[0] = static_cast( mprojectedPointIn2D[0] - 0.5 ); projectedPointIn2D[1] = static_cast( mprojectedPointIn2D[1] - 0.5 ); if ( sliceGeometry->IsIndexInside( mprojectedPointIn2D ) ) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetWorldPosition() << " (index coordinates " << mprojectedPointIn2D << ") IS in reference slice" << std::endl; // 3.2.1 Remember Y cursor position and initial seed point //m_ScreenYPositionAtStart = static_cast(positionEvent->GetDisplayPosition()[1]); m_LastScreenPosition = ApplicationCursor::GetInstance()->GetCursorPosition(); m_ScreenYDifference = 0; m_SeedPointMemoryOffset = projectedPointIn2D[1] * m_OriginalPicSlice->n[0] + projectedPointIn2D[0]; m_LastWorkingSeed = m_SeedPointMemoryOffset; // remember for skeletonization if ( m_SeedPointMemoryOffset < static_cast( m_OriginalPicSlice->n[0] * m_OriginalPicSlice->n[1] ) && m_SeedPointMemoryOffset >= 0 ) { // 3.2.2 Get level window from reference DataNode // Use some logic to determine initial gray value bounds LevelWindow lw(0, 500); m_ToolManager->GetReferenceData(0)->GetLevelWindow(lw); // will fill lw if levelwindow property is present, otherwise won't touch it. ScalarType currentVisibleWindow = lw.GetWindow(); if (!mitk::Equal(currentVisibleWindow, m_VisibleWindow)) { m_InitialLowerThreshold = currentVisibleWindow / 20.0; m_InitialUpperThreshold = currentVisibleWindow / 20.0; m_LowerThreshold = m_InitialLowerThreshold; m_UpperThreshold = m_InitialUpperThreshold; // 3.2.3. Actually perform region growing mitkIpPicDescriptor* result = PerformRegionGrowingAndUpdateContour(); ipMITKSegmentationFree( result); // display the contour FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); m_FillFeedbackContour = true; } } return true; } return false; } /** If in region growing mode (m_ReferenceSlice != NULL), then 1. Calculate the new thresholds from mouse position (relative to first position) 2. Perform a new region growing and update the feedback contour */ bool mitk::RegionGrowingTool::OnMouseMoved(Action* action, const StateEvent* stateEvent) { - if (FeedbackContourTool::OnMouseMoved( action, stateEvent )) + if ( FeedbackContourTool::CanHandleEvent(stateEvent) > 0.0 ) { if ( m_ReferenceSlice.IsNotNull() && m_OriginalPicSlice ) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (positionEvent) { ApplicationCursor* cursor = ApplicationCursor::GetInstance(); if (!cursor) return false; m_ScreenYDifference += cursor->GetCursorPosition()[1] - m_LastScreenPosition[1]; cursor->SetCursorPosition( m_LastScreenPosition ); m_LowerThreshold = std::max(0.0, m_InitialLowerThreshold - m_ScreenYDifference * m_MouseDistanceScaleFactor); m_UpperThreshold = std::max(0.0, m_InitialUpperThreshold - m_ScreenYDifference * m_MouseDistanceScaleFactor); // 2. Perform region growing again and show the result mitkIpPicDescriptor* result = PerformRegionGrowingAndUpdateContour(); ipMITKSegmentationFree( result ); // 3. Update the contour mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } return true; } /** If the feedback contour should be filled, then it is done here. (Contour is NOT filled, when skeletonization is done but no nice cut was found) */ bool mitk::RegionGrowingTool::OnMouseReleased(Action* action, const StateEvent* stateEvent) { - if (FeedbackContourTool::OnMouseReleased( action, stateEvent )) + if ( FeedbackContourTool::CanHandleEvent(stateEvent) > 0.0 ) { // 1. If we have a working slice, use the contour to fill a new piece on segmentation on it (or erase a piece that was selected by ipMITKSegmentationGetCutPoints) if ( m_WorkingSlice.IsNotNull() && m_OriginalPicSlice ) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (positionEvent) { // remember parameters for next time m_InitialLowerThreshold = m_LowerThreshold; m_InitialUpperThreshold = m_UpperThreshold; if (m_FillFeedbackContour) { // 3. use contour to fill a region in our working slice Contour* feedbackContour( FeedbackContourTool::GetFeedbackContour() ); if (feedbackContour) { Contour::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( m_WorkingSlice, feedbackContour, false, false ); // false: don't add any 0.5 // false: don't constrain the contour to the image's inside if (projectedContour.IsNotNull()) { FeedbackContourTool::FillContourInSlice( projectedContour, m_WorkingSlice, m_PaintingPixelValue ); const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); //MITK_DEBUG << "OnMouseReleased: writing back to dimension " << affectedDimension << ", slice " << affectedSlice << " in working image" << std::endl; // 4. write working slice back into image volume this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice); } } } FeedbackContourTool::SetFeedbackContourVisible(false); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); } } } m_ReferenceSlice = NULL; // don't leak m_WorkingSlice = NULL; m_OriginalPicSlice = NULL; return true; } /** Uses ipSegmentation algorithms to do the actual region growing. The result (binary image) is first smoothed by a 5x5 circle mask, then its contour is extracted and converted to MITK coordinates. */ mitkIpPicDescriptor* mitk::RegionGrowingTool::PerformRegionGrowingAndUpdateContour() { // 1. m_OriginalPicSlice and m_SeedPointMemoryOffset are set to sensitive values, as well as m_LowerThreshold and m_UpperThreshold assert (m_OriginalPicSlice); if (m_OriginalPicSlice->n[0] != 256 || m_OriginalPicSlice->n[1] != 256) // ??? assert( (m_SeedPointMemoryOffset < static_cast( m_OriginalPicSlice->n[0] * m_OriginalPicSlice->n[1] )) && (m_SeedPointMemoryOffset >= 0) ); // inside the image // 2. ipSegmentation is used to perform region growing float ignored; int oneContourOffset( 0 ); mitkIpPicDescriptor* regionGrowerResult = ipMITKSegmentationGrowRegion4N( m_OriginalPicSlice, m_SeedPointMemoryOffset, // seed point true, // grayvalue interval relative to seed point gray value? m_LowerThreshold, m_UpperThreshold, 0, // continue until done (maxIterations == 0) NULL, // allocate new memory (only this time, on mouse move we'll reuse the old buffer) oneContourOffset, // a pixel that is near the resulting contour ignored // ignored by us ); if (!regionGrowerResult || oneContourOffset == -1) { Contour::Pointer dummyContour = Contour::New(); dummyContour->Initialize(); FeedbackContourTool::SetFeedbackContour( *dummyContour ); if (regionGrowerResult) ipMITKSegmentationFree(regionGrowerResult); return NULL; } // 3. We smooth the result a little to reduce contour complexity bool smoothResult( true ); // currently fixed, perhaps remove else block mitkIpPicDescriptor* smoothedRegionGrowerResult; if (smoothResult) { // Smooth the result (otherwise very detailed contour) smoothedRegionGrowerResult = SmoothIPPicBinaryImage( regionGrowerResult, oneContourOffset ); ipMITKSegmentationFree( regionGrowerResult ); } else { smoothedRegionGrowerResult = regionGrowerResult; } // 4. convert the result of region growing into a mitk::Contour // At this point oneContourOffset could be useless, if smoothing destroyed a thin bridge. In these // cases, we have two or more unconnected segmentation regions, and we don't know, which one is touched by oneContourOffset. // In the bad case, the contour is not the one around our seedpoint, so the result looks very strange to the user. // -> we remove the point where the contour started so far. Then we look from the bottom of the image for the first segmentation pixel // and start another contour extraction from there. This is done, until the seedpoint is inside the contour int numberOfContourPoints( 0 ); int newBufferSize( 0 ); float* contourPoints = ipMITKSegmentationGetContour8N( smoothedRegionGrowerResult, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc if (contourPoints) { while ( !ipMITKSegmentationIsInsideContour( contourPoints, // contour numberOfContourPoints, // points in contour m_SeedPointMemoryOffset % smoothedRegionGrowerResult->n[0], // test point x m_SeedPointMemoryOffset / smoothedRegionGrowerResult->n[0] // test point y ) ) { // we decide that this cannot be part of the segmentation because the seedpoint is not contained in the contour (fill the 4-neighborhood with 0) ipMITKSegmentationReplaceRegion4N( smoothedRegionGrowerResult, oneContourOffset, 0 ); // move the contour offset to the last row (x position of the seed point) int rowLength = smoothedRegionGrowerResult->n[0]; // number of pixels in a row oneContourOffset = m_SeedPointMemoryOffset % smoothedRegionGrowerResult->n[0] // x of seed point + rowLength*(smoothedRegionGrowerResult->n[1]-1); // y of last row while ( oneContourOffset >=0 && (*(static_cast(smoothedRegionGrowerResult->data) + oneContourOffset) == 0) ) { oneContourOffset -= rowLength; // if pixel at data+oneContourOffset is 0, then move up one row } if ( oneContourOffset < 0 ) { break; // just use the last contour we found } free(contourPoints); // release contour memory contourPoints = ipMITKSegmentationGetContour8N( smoothedRegionGrowerResult, oneContourOffset, numberOfContourPoints, newBufferSize ); // memory allocated with malloc } // copy point from float* to mitk::Contour Contour::Pointer contourInImageIndexCoordinates = Contour::New(); contourInImageIndexCoordinates->Initialize(); Point3D newPoint; for (int index = 0; index < numberOfContourPoints; ++index) { newPoint[0] = contourPoints[ 2 * index + 0 ]; newPoint[1] = contourPoints[ 2 * index + 1 ]; newPoint[2] = 0; contourInImageIndexCoordinates->AddVertex( newPoint - 0.5); } free(contourPoints); Contour::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( m_ReferenceSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for ipSegmentation correctio FeedbackContourTool::SetFeedbackContour( *contourInWorldCoordinates ); } // 5. Result HAS TO BE freed by caller, contains the binary region growing result return smoothedRegionGrowerResult; } /** Helper method for SmoothIPPicBinaryImage. Smoothes a given part of and image. \param sourceImage The original binary image. \param dest The smoothed image (will be written without bounds checking). \param contourOfs One offset of the contour. Is updated if a pixel is changed (which might change the contour). \param maskOffsets Memory offsets that describe the smoothing mask. \param maskSize Entries of the mask. \param startOffset First pixel that should be smoothed using this mask. \param endOffset Last pixel that should be smoothed using this mask. */ void mitk::RegionGrowingTool::SmoothIPPicBinaryImageHelperForRows( mitkIpPicDescriptor* sourceImage, mitkIpPicDescriptor* dest, int &contourOfs, int* maskOffsets, int maskSize, int startOffset, int endOffset ) { // work on the very first row ipMITKSegmentationTYPE* current; ipMITKSegmentationTYPE* source = ((ipMITKSegmentationTYPE*)sourceImage->data) + startOffset; // + 1! don't read at start-1 ipMITKSegmentationTYPE* end = ((ipMITKSegmentationTYPE*)dest->data) + endOffset; int ofs = startOffset; int minority = (maskSize - 1) / 2; for (current = ((ipMITKSegmentationTYPE*)dest->data) + startOffset; current minority) { *current = 1; contourOfs = ofs; } else { *current = 0; } ++source; ++ofs; } } /** Smoothes a binary ipPic image with a 5x5 mask. The image borders (some first and last rows) are treated differently. */ mitkIpPicDescriptor* mitk::RegionGrowingTool::SmoothIPPicBinaryImage( mitkIpPicDescriptor* image, int &contourOfs, mitkIpPicDescriptor* dest ) { if (!image) return NULL; // Original code from /trunk/mbi-qm/Qmitk/Qmitk2DSegTools/RegionGrowerTool.cpp (first version by T. Boettger?). Reformatted and documented and restructured. #define MSK_SIZE5x5 21 #define MSK_SIZE3x3 5 #define MSK_SIZE3x1 3 // mask is an array of coordinates that form a rastered circle like this // // OOO // OOOOO // OOOOO // OOOOO // OOO // // int mask5x5[MSK_SIZE5x5][2] = { /******/ {-1,-2}, {0,-2}, {1,-2}, /*****/ {-2,-1}, {-1,-1}, {0,-1}, {1,-1}, {2,-1}, {-2, 0}, {-1, 0}, {0, 0}, {1, 0}, {2, 0}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1}, {2, 1}, /******/ {-1, 2}, {0, 2}, {1, 2} /*****/ }; int mask3x3[MSK_SIZE3x3][2] = { /******/ {0,-1}, /*****/ {-1, 0}, {0, 0}, {1, 0}, /******/ {0, 1} /*****/ }; int mask3x1[MSK_SIZE3x1][2] = { {-1, 0}, {0, 0}, {1, 0} }; // The following lines iterate over all the pixels of a (sliced) image (except the first and last three rows). // For each pixel, all the coordinates around it (according to mask) are evaluated (this means 21 pixels). // If more than 10 of the evaluated pixels are non-zero, then the central pixel is set to 1, else to 0. // This is determining a majority. If there is no clear majority, then the central pixel itself "decides". int maskOffset5x5[MSK_SIZE5x5]; int line = image->n[0]; for (int i=0; in[0]; int spareOut1Rows = 1*image->n[0]; if ( image->n[1] > 0 ) SmoothIPPicBinaryImageHelperForRows( image, dest, contourOfs, maskOffset3x1, MSK_SIZE3x1, 1, dest->n[0] ); if ( image->n[1] > 3 ) SmoothIPPicBinaryImageHelperForRows( image, dest, contourOfs, maskOffset3x3, MSK_SIZE3x3, spareOut1Rows, dest->n[0]*3 ); if ( image->n[1] > 6 ) SmoothIPPicBinaryImageHelperForRows( image, dest, contourOfs, maskOffset5x5, MSK_SIZE5x5, spareOut3Rows, dest->n[0]*dest->n[1] - spareOut3Rows ); if ( image->n[1] > 8 ) SmoothIPPicBinaryImageHelperForRows( image, dest, contourOfs, maskOffset3x3, MSK_SIZE3x3, dest->n[0]*dest->n[1] -spareOut3Rows, dest->n[0]*dest->n[1] - spareOut1Rows ); if ( image->n[1] > 10) SmoothIPPicBinaryImageHelperForRows( image, dest, contourOfs, maskOffset3x1, MSK_SIZE3x1, dest->n[0]*dest->n[1] -spareOut1Rows, dest->n[0]*dest->n[1] - 1 ); // correction for first pixel (sorry for the ugliness) if ( *((ipMITKSegmentationTYPE*)(dest->data)+1) == 1 ) { *((ipMITKSegmentationTYPE*)(dest->data)+0) = 1; } if (dest->n[0] * dest->n[1] > 2) { // correction for last pixel if ( *((ipMITKSegmentationTYPE*)(dest->data)+dest->n[0]*dest->n[1]-2) == 1 ) { *((ipMITKSegmentationTYPE*)(dest->data)+dest->n[0]*dest->n[1]-1) = 1; } } return dest; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index b6185dedbd..edb58fe711 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,386 +1,347 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkDataStorage.h" #include "mitkBaseRenderer.h" #include "mitkPlaneGeometry.h" #include "mitkExtractImageFilter.h" #include "mitkExtractDirectedPlaneImageFilter.h" //Include of the new ImageExtractor #include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkPlanarCircle.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkGetModuleContext.h" //Includes for 3DSurfaceInterpolation #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" //includes for resling and overwriting #include #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #define ROUND(a) ((a)>0 ? (int)((a)+0.5) : -(int)(0.5-(a))) mitk::SegTool2D::SegTool2D(const char* type) :Tool(type), m_LastEventSender(NULL), m_LastEventSlice(0), m_Contourmarkername ("Position"), m_ShowMarkerNodes (true) { - // great magic numbers - CONNECT_ACTION( 80, OnMousePressed ); - CONNECT_ACTION( 90, OnMouseMoved ); - CONNECT_ACTION( 42, OnMouseReleased ); - CONNECT_ACTION( 49014, OnInvertLogic ); - - } mitk::SegTool2D::~SegTool2D() { } -bool mitk::SegTool2D::OnMousePressed (Action*, const StateEvent* stateEvent) +float mitk::SegTool2D::CanHandleEvent( StateEvent const *stateEvent) const { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); - if (!positionEvent) return false; - - if ( positionEvent->GetSender()->GetMapperID() != BaseRenderer::Standard2D ) return false; // we don't want anything but 2D - - m_LastEventSender = positionEvent->GetSender(); - m_LastEventSlice = m_LastEventSender->GetSlice(); - - return true; -} + if (!positionEvent) return 0.0; -bool mitk::SegTool2D::OnMouseMoved (Action*, const StateEvent* stateEvent) -{ - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); - if (!positionEvent) return false; + if ( positionEvent->GetSender()->GetMapperID() != BaseRenderer::Standard2D ) return 0.0; // we don't want anything but 2D - if ( m_LastEventSender != positionEvent->GetSender() ) return false; - if ( m_LastEventSlice != m_LastEventSender->GetSlice() ) return false; + if( m_LastEventSender != positionEvent->GetSender()) return 0.0; + if( m_LastEventSlice != positionEvent->GetSender()->GetSlice() ) return 0.0; - return true; + return 1.0; } -bool mitk::SegTool2D::OnMouseReleased(Action*, const StateEvent* stateEvent) -{ - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); - if (!positionEvent) return false; - - if ( m_LastEventSender != positionEvent->GetSender() ) return false; - if ( m_LastEventSlice != m_LastEventSender->GetSlice() ) return false; - - return true; -} - -bool mitk::SegTool2D::OnInvertLogic(Action*, const StateEvent* stateEvent) -{ - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); - if (!positionEvent) return false; - - if ( m_LastEventSender != positionEvent->GetSender() ) return false; - if ( m_LastEventSlice != m_LastEventSender->GetSlice() ) return false; - - return true; -} 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.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal0.Get_vnl_vector()) ); imageNormal1.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal1.Get_vnl_vector()) ); imageNormal2.Set_vnl_vector( vnl_cross_3d(normal.Get_vnl_vector(),imageNormal2.Get_vnl_vector()) ); double eps( 0.00001 ); // transversal 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 Geometry3D* 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; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PositionEvent* positionEvent, const Image* image) { if (!positionEvent) return NULL; assert( positionEvent->GetSender() ); // sure, right? unsigned int timeStep = positionEvent->GetSender()->GetTimeStep( image ); // get the timestep of the visible part (time-wise) of the image // first, we determine, which slice is affected const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); if ( !image || !planeGeometry ) return NULL; //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->GetTimeSlicedGeometry()->GetGeometry3D( timeStep ) ); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); /*============= BEGIN undo feature block ========================*/ //specify the undo operation with the non edited slice m_undoOperation = new DiffSliceOperation(const_cast(image), extractor->GetVtkOutput(), slice->GetGeometry(), timeStep, const_cast(planeGeometry)); /*============= END undo feature block ========================*/ return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const PositionEvent* positionEvent) { DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if ( !workingNode ) return NULL; Image* workingImage = dynamic_cast(workingNode->GetData()); if ( !workingImage ) return NULL; return GetAffectedImageSliceAs2DImage( positionEvent, workingImage ); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PositionEvent* positionEvent) { DataNode* referenceNode( m_ToolManager->GetReferenceData(0) ); if ( !referenceNode ) return NULL; Image* referenceImage = dynamic_cast(referenceNode->GetData()); if ( !referenceImage ) return NULL; return GetAffectedImageSliceAs2DImage( positionEvent, referenceImage ); } void mitk::SegTool2D::WriteBackSegmentationResult (const PositionEvent* positionEvent, Image* slice) { const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); Image* image = dynamic_cast(workingNode->GetData()); unsigned int timeStep = positionEvent->GetSender()->GetTimeStep( image ); //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' reslice->SetInputSlice(slice->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->SetTimeStep( timeStep ); extractor->SetWorldGeometry( planeGeometry ); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry( image->GetTimeSlicedGeometry()->GetGeometry3D( timeStep ) ); extractor->Modified(); extractor->Update(); //the image was modified within the pipeline, but not marked so image->Modified(); /*============= BEGIN undo feature block ========================*/ //specify the undo operation with the edited slice m_doOperation = new DiffSliceOperation(image, extractor->GetVtkOutput(),slice->GetGeometry(), timeStep, const_cast(planeGeometry)); //create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent( DiffSliceOperationApplier::GetInstance(), m_doOperation, m_undoOperation, "Segmentation" ); //add it to the undo controller UndoController::GetCurrentUndoModel()->SetOperationEvent( undoStackItem ); //clear the pointers as the operation are stored in the undocontroller and also deleted from there m_undoOperation = NULL; m_doOperation = NULL; /*============= END undo feature block ========================*/ slice->DisconnectPipeline(); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); contourExtractor->SetInput(slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() > 0 ) { unsigned int pos = this->AddContourmarker(positionEvent); mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); PlanePositionManagerService* service = dynamic_cast(mitk::GetModuleContext()->GetService(serviceRef)); mitk::SurfaceInterpolationController::GetInstance()->AddNewContour( contour, service->GetPlanePosition(pos)); contour->DisconnectPipeline(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } unsigned int mitk::SegTool2D::AddContourmarker ( const PositionEvent* positionEvent ) { const mitk::Geometry2D* plane = dynamic_cast (dynamic_cast< const mitk::SlicedGeometry3D*>( positionEvent->GetSender()->GetSliceNavigationController()->GetCurrentGeometry3D())->GetGeometry2D(0)); mitk::ServiceReference serviceRef = mitk::GetModuleContext()->GetServiceReference(); PlanePositionManagerService* service = dynamic_cast(mitk::GetModuleContext()->GetService(serviceRef)); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(plane, positionEvent->GetSender()->GetSliceNavigationController()->GetSlice()->GetPos()); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); contourMarker->SetGeometry2D( const_cast(plane)); std::stringstream markerStream; mitk::DataNode* workingNode (m_ToolManager->GetWorkingData(0)); 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, positionEvent->GetSender() ); rotatedContourNode->SetProperty( "includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty( "helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); if (plane) { 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 ( mitk::DataStorage::SetOfObjects::const_iterator 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 ) { 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 << " http://bugs.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; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 7a39fff44c..63f3f81881 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,138 +1,140 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "SegmentationExports.h" #include "mitkTool.h" #include "mitkImage.h" #include "mitkStateEvent.h" #include "mitkPositionEvent.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkInteractionConst.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 Segmentation_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 affectedDimension The image dimension, which is constant for all points in the plane, e.g. Transversal --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice( const Image* image, const PlaneGeometry* plane, int& affectedDimension, int& affectedSlice ); void SetShowMarkerNodes(bool); protected: SegTool2D(); // purposely hidden SegTool2D(const char*); // purposely hidden virtual ~SegTool2D(); - virtual bool OnMousePressed (Action*, const StateEvent*); - virtual bool OnMouseMoved (Action*, const StateEvent*); - virtual bool OnMouseReleased(Action*, const StateEvent*); - virtual bool OnInvertLogic (Action*, const StateEvent*); + /** + * \brief Calculates how good the data, this statemachine handles, is hit by the event. + * + */ + virtual float CanHandleEvent( StateEvent const *stateEvent) const; /** \brief Extract the slice of an image that the user just scribbles on. \return NULL if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position. */ Image::Pointer GetAffectedImageSliceAs2DImage(const PositionEvent*, const Image* image); /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return NULL 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 PositionEvent*); /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return NULL 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 PositionEvent*); void WriteBackSegmentationResult (const PositionEvent*, Image*); /** \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 */ unsigned int AddContourmarker ( const PositionEvent* ); void InteractiveSegmentationBugMessage( const std::string& message ); - - private: + BaseRenderer* m_LastEventSender; unsigned int m_LastEventSlice; + + private: //The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes; bool m_3DInterpolationEnabled; DiffSliceOperation* m_doOperation; DiffSliceOperation* m_undoOperation; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp index a4f50649e3..e11243dfa0 100644 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp @@ -1,310 +1,320 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSetRegionTool.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "ipSegmentation.h" #include "mitkBaseRenderer.h" #include "mitkImageDataItem.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" mitk::SetRegionTool::SetRegionTool(int paintingPixelValue) :FeedbackContourTool("PressMoveReleaseWithCTRLInversion"), m_PaintingPixelValue(paintingPixelValue), m_FillContour(false), m_StatusFillWholeSlice(false) { + // great magic numbers + CONNECT_ACTION( 80, OnMousePressed ); + //CONNECT_ACTION( 90, OnMouseMoved ); + CONNECT_ACTION( 42, OnMouseReleased ); + CONNECT_ACTION( 49014, OnInvertLogic ); + } mitk::SetRegionTool::~SetRegionTool() { } void mitk::SetRegionTool::Activated() { Superclass::Activated(); } void mitk::SetRegionTool::Deactivated() { Superclass::Deactivated(); } bool mitk::SetRegionTool::OnMousePressed (Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnMousePressed( action, stateEvent )) return false; - const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; + m_LastEventSender = positionEvent->GetSender(); + m_LastEventSlice = m_LastEventSender->GetSlice(); + + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; + + // 1. Get the working image Image::Pointer workingSlice = FeedbackContourTool::GetAffectedWorkingSlice( positionEvent ); if ( workingSlice.IsNull() ) return false; // can't do anything without the segmentation // if click was outside the image, don't continue const Geometry3D* sliceGeometry = workingSlice->GetGeometry(); itk::Index<2> projectedPointIn2D; sliceGeometry->WorldToIndex( positionEvent->GetWorldPosition(), projectedPointIn2D ); if ( !sliceGeometry->IsIndexInside( projectedPointIn2D ) ) { MITK_ERROR << "point apparently not inside segmentation slice" << std::endl; return false; // can't use that as a seed point } // Convert to ipMITKSegmentationTYPE (because ipMITKSegmentationGetContour8N relys on that data type) itk::Image< ipMITKSegmentationTYPE, 2 >::Pointer correctPixelTypeImage; CastToItkImage( workingSlice, correctPixelTypeImage ); assert (correctPixelTypeImage.IsNotNull() ); // possible bug in CastToItkImage ? // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479: // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 && aSpacing[2]>0' failed // solution here: we overwrite it with an unity matrix itk::Image< ipMITKSegmentationTYPE, 2 >::DirectionType imageDirection; imageDirection.SetIdentity(); correctPixelTypeImage->SetDirection(imageDirection); Image::Pointer temporarySlice = Image::New(); // temporarySlice = ImportItkImage( correctPixelTypeImage ); CastToMitkImage( correctPixelTypeImage, temporarySlice ); // check index positions mitkIpPicDescriptor* originalPicSlice = mitkIpPicNew(); CastToIpPicDescriptor( temporarySlice, originalPicSlice ); int m_SeedPointMemoryOffset = projectedPointIn2D[1] * originalPicSlice->n[0] + projectedPointIn2D[0]; if ( m_SeedPointMemoryOffset >= static_cast( originalPicSlice->n[0] * originalPicSlice->n[1] ) || m_SeedPointMemoryOffset < 0 ) { MITK_ERROR << "Memory offset calculation if mitk::SetRegionTool has some serious flaw! Aborting.." << std::endl; return false; } // 2. Determine the contour that surronds the selected "piece of the image" // find a contour seed point unsigned int oneContourOffset = static_cast( m_SeedPointMemoryOffset ); // safe because of earlier check if m_SeedPointMemoryOffset < 0 /** * The logic of finding a starting point for the contour is the following: * * - If the initial seed point is 0, we are either inside a hole or outside of every segmentation. * We move to the right until we hit a 1, which must be part of a contour. * * - If the initial seed point is 1, then ... * we now do the same (running to the right) until we hit a 1 * * In both cases the found contour point is used to extract a contour and * then a test is applied to find out if the initial seed point is contained * in the contour. If this is the case, filling should be applied, otherwise * nothing is done. */ unsigned int size = originalPicSlice->n[0] * originalPicSlice->n[1]; /* unsigned int rowSize = originalPicSlice->n[0]; */ ipMITKSegmentationTYPE* data = static_cast(originalPicSlice->data); if ( data[oneContourOffset] == 0 ) // initial seed 0 { for ( ; oneContourOffset < size; ++oneContourOffset ) { if ( data[oneContourOffset] > 0 ) break; } } else if ( data[oneContourOffset] == 1 ) // initial seed 1 { unsigned int lastValidPixel = size-1; // initialization, will be changed lateron bool inSeg = true; // inside segmentation? for ( ; oneContourOffset < size; ++oneContourOffset ) { if ( ( data[oneContourOffset] == 0 ) && inSeg ) // pixel 0 and inside-flag set: this happens at the first pixel outside a filled region { inSeg = false; lastValidPixel = oneContourOffset - 1; // store the last pixel position inside a filled region break; } else // pixel 1, inside-flag doesn't matter: this happens while we are inside a filled region { inSeg = true; // first iteration lands here } } oneContourOffset = lastValidPixel; } else { MITK_ERROR << "Fill/Erase was never intended to work with other than binary images." << std::endl; m_FillContour = false; return false; } if (oneContourOffset == size) // nothing found until end of slice { m_FillContour = false; return false; } int numberOfContourPoints( 0 ); int newBufferSize( 0 ); //MITK_INFO << "getting contour from offset " << oneContourOffset << " ("<n[0]<<","<n[0]<<")"< 0); bool cursorInsideContour = ipMITKSegmentationIsInsideContour( contourPoints, numberOfContourPoints, projectedPointIn2D[0], projectedPointIn2D[1]); // decide if contour should be filled or not m_FillContour = cursorInsideContour; if (m_FillContour) { // copy point from float* to mitk::Contour Contour::Pointer contourInImageIndexCoordinates = Contour::New(); contourInImageIndexCoordinates->Initialize(); Point3D newPoint; for (int index = 0; index < numberOfContourPoints; ++index) { newPoint[0] = contourPoints[ 2 * index + 0 ]; newPoint[1] = contourPoints[ 2 * index + 1]; newPoint[2] = 0; contourInImageIndexCoordinates->AddVertex( newPoint - 0.5 ); } m_SegmentationContourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true, correct the result from ipMITKSegmentationGetContour8N // 3. Show the contour FeedbackContourTool::SetFeedbackContour( *m_SegmentationContourInWorldCoordinates ); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } // always generate a second contour, containing the whole image (used when CTRL is pressed) { // copy point from float* to mitk::Contour Contour::Pointer contourInImageIndexCoordinates = Contour::New(); contourInImageIndexCoordinates->Initialize(); Point3D newPoint; newPoint[0] = 0; newPoint[1] = 0; newPoint[2] = 0.0; contourInImageIndexCoordinates->AddVertex( newPoint ); newPoint[0] = originalPicSlice->n[0]; newPoint[1] = 0; newPoint[2] = 0.0; contourInImageIndexCoordinates->AddVertex( newPoint ); newPoint[0] = originalPicSlice->n[0]; newPoint[1] = originalPicSlice->n[1]; newPoint[2] = 0.0; contourInImageIndexCoordinates->AddVertex( newPoint ); newPoint[0] = 0; newPoint[1] = originalPicSlice->n[1]; newPoint[2] = 0.0; contourInImageIndexCoordinates->AddVertex( newPoint ); m_WholeImageContourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true, correct the result from ipMITKSegmentationGetContour8N // 3. Show the contour FeedbackContourTool::SetFeedbackContour( *m_SegmentationContourInWorldCoordinates ); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } free(contourPoints); return true; } bool mitk::SetRegionTool::OnMouseReleased(Action* action, const StateEvent* stateEvent) { // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's working image corresponds to that FeedbackContourTool::SetFeedbackContourVisible(false); const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); if (!m_FillContour && !m_StatusFillWholeSlice) return true; - if (!FeedbackContourTool::OnMouseReleased( action, stateEvent )) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; DataNode* workingNode( m_ToolManager->GetWorkingData(0) ); if (!workingNode) return false; Image* image = dynamic_cast(workingNode->GetData()); const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); if ( !image || !planeGeometry ) return false; Image::Pointer slice = FeedbackContourTool::GetAffectedImageSliceAs2DImage( positionEvent, image ); if ( slice.IsNull() ) { MITK_ERROR << "Unable to extract slice." << std::endl; return false; } Contour* feedbackContour( FeedbackContourTool::GetFeedbackContour() ); Contour::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( slice, feedbackContour, false, false ); // false: don't add 0.5 (done by FillContourInSlice) // false: don't constrain the contour to the image's inside if (projectedContour.IsNull()) return false; FeedbackContourTool::FillContourInSlice( projectedContour, slice, m_PaintingPixelValue ); this->WriteBackSegmentationResult(positionEvent, slice); m_WholeImageContourInWorldCoordinates = NULL; m_SegmentationContourInWorldCoordinates = NULL; return true; } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ bool mitk::SetRegionTool::OnInvertLogic(Action* action, const StateEvent* stateEvent) { - if (!FeedbackContourTool::OnInvertLogic(action, stateEvent)) return false; + if ( FeedbackContourTool::CanHandleEvent(stateEvent) < 1.0 ) return false; const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; if (m_StatusFillWholeSlice) { // use contour extracted from image data if (m_SegmentationContourInWorldCoordinates.IsNotNull()) FeedbackContourTool::SetFeedbackContour( *m_SegmentationContourInWorldCoordinates ); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } else { // use some artificial contour if (m_WholeImageContourInWorldCoordinates.IsNotNull()) FeedbackContourTool::SetFeedbackContour( *m_WholeImageContourInWorldCoordinates ); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } m_StatusFillWholeSlice = !m_StatusFillWholeSlice; return true; }