diff --git a/Modules/Bundles/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Modules/Bundles/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index eaf2c40a52..821b0bbe69 100644 --- a/Modules/Bundles/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Modules/Bundles/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,924 +1,937 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision: 1.12 $ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkDataNodeObject.h" #include "mitkProperties.h" #include "mitkSegTool2D.h" #include "mitkGlobalInteraction.h" #include "QmitkStdMultiWidget.h" #include "QmitkNewSegmentationDialog.h" #include #include #include "QmitkSegmentationView.h" #include "QmitkSegmentationPostProcessing.h" #include "QmitkSegmentationOrganNamesHandling.cpp" #include #include #include "mitkVtkResliceInterpolationProperty.h" // public methods QmitkSegmentationView::QmitkSegmentationView() :m_Parent(NULL) ,m_Controls(NULL) ,m_MultiWidget(NULL) ,m_RenderingManagerObserverTag(0) { } QmitkSegmentationView::~QmitkSegmentationView() { // delete m_PostProcessing; delete m_Controls; } void QmitkSegmentationView::NewNodesGenerated() { // ForceDisplayPreferencesUponAllImages(); } void QmitkSegmentationView::NewNodeObjectsGenerated(mitk::ToolManager::DataVectorType* nodes) { if (!nodes) return; mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); if (!toolManager) return; for (mitk::ToolManager::DataVectorType::iterator iter = nodes->begin(); iter != nodes->end(); ++iter) { this->FireNodeSelected( *iter ); // only last iteration meaningful, multiple generated objects are not taken into account here } } void QmitkSegmentationView::Activated() { // should be moved to ::BecomesVisible() or similar if( m_Controls ) { m_Controls->m_ManualToolSelectionBox->setEnabled( true ); m_Controls->m_OrganToolSelectionBox->setEnabled( true ); m_Controls->m_LesionToolSelectionBox->setEnabled( true ); m_Controls->m_SlicesInterpolator->EnableInterpolation( m_Controls->widgetStack->currentWidget() == m_Controls->pageManual ); itk::ReceptorMemberCommand::Pointer command1 = itk::ReceptorMemberCommand::New(); command1->SetCallbackFunction( this, &QmitkSegmentationView::RenderingManagerReinitialized ); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver( mitk::RenderingManagerViewsInitializedEvent(), command1 ); } } void QmitkSegmentationView::Deactivated() { if( m_Controls ) { mitk::RenderingManager::GetInstance()->RemoveObserver( m_RenderingManagerObserverTag ); m_Controls->m_ManualToolSelectionBox->setEnabled( false ); //deactivate all tools m_Controls->m_ManualToolSelectionBox->GetToolManager()->ActivateTool(-1); m_Controls->m_OrganToolSelectionBox->setEnabled( false ); m_Controls->m_LesionToolSelectionBox->setEnabled( false ); m_Controls->m_SlicesInterpolator->EnableInterpolation( false ); } } void QmitkSegmentationView::StdMultiWidgetAvailable( QmitkStdMultiWidget& stdMultiWidget ) { SetMultiWidget(&stdMultiWidget); } void QmitkSegmentationView::StdMultiWidgetNotAvailable() { SetMultiWidget(NULL); } void QmitkSegmentationView::StdMultiWidgetClosed( QmitkStdMultiWidget& /*stdMultiWidget*/ ) { SetMultiWidget(NULL); } void QmitkSegmentationView::SetMultiWidget(QmitkStdMultiWidget* multiWidget) { if (m_MultiWidget) { mitk::SlicesCoordinator* coordinator = m_MultiWidget->GetSlicesRotator(); if (coordinator) { coordinator->RemoveObserver( m_SlicesRotationObserverTag1 ); } coordinator = m_MultiWidget->GetSlicesSwiveller(); if (coordinator) { coordinator->RemoveObserver( m_SlicesRotationObserverTag2 ); } } // save the current multiwidget as the working widget m_MultiWidget = multiWidget; if (m_MultiWidget) { mitk::SlicesCoordinator* coordinator = m_MultiWidget->GetSlicesRotator(); if (coordinator) { itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction( this, &QmitkSegmentationView::SliceRotation ); m_SlicesRotationObserverTag1 = coordinator->AddObserver( mitk::SliceRotationEvent(), command2 ); } coordinator = m_MultiWidget->GetSlicesSwiveller(); if (coordinator) { itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction( this, &QmitkSegmentationView::SliceRotation ); m_SlicesRotationObserverTag2 = coordinator->AddObserver( mitk::SliceRotationEvent(), command2 ); } } if (m_Parent) { m_Parent->setEnabled(m_MultiWidget); } // tell the interpolation about toolmanager and multiwidget (and data storage) if (m_Controls && m_MultiWidget) { mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); m_Controls->m_SlicesInterpolator->SetDataStorage( *(this->GetDefaultDataStorage())); m_Controls->m_SlicesInterpolator->Initialize( toolManager, m_MultiWidget ); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences*) { ForceDisplayPreferencesUponAllImages(); } void QmitkSegmentationView::RenderingManagerReinitialized(const itk::EventObject&) { CheckImageAlignment(); } void QmitkSegmentationView::SliceRotation(const itk::EventObject&) { CheckImageAlignment(); } // protected slots void QmitkSegmentationView::CreateNewSegmentation() { mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull()) { if (image->GetDimension()>2) { // ask about the name and organ type of the new segmentation QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog( m_Parent ); // needs a QWidget as parent, "this" is not QWidget QString storedList = QString::fromStdString( this->GetPreferences()->GetByteArray("Organ-Color-List","") ); QStringList organColors; if (storedList.isEmpty()) { organColors = GetDefaultOrganColorString(); } else { /* a couple of examples of how organ names are stored: a simple item is built up like 'name#AABBCC' where #AABBCC is the hexadecimal notation of a color as known from HTML items are stored separated by ';' this makes it necessary to escape occurrences of ';' in name. otherwise the string "hugo;ypsilon#AABBCC;eugen#AABBCC" could not be parsed as two organs but we would get "hugo" and "ypsilon#AABBCC" and "eugen#AABBCC" so the organ name "hugo;ypsilon" is stored as "hugo\;ypsilon" and must be unescaped after loading the following lines could be one split with Perl's negative lookbehind */ // recover string list from BlueBerry view's preferences QString storedString = QString::fromStdString( this->GetPreferences()->GetByteArray("Organ-Color-List","") ); MITK_DEBUG << "storedString: " << storedString.toStdString(); // match a string consisting of any number of repetitions of either "anything but ;" or "\;". This matches everything until the next unescaped ';' QRegExp onePart("(?:[^;]|\\\\;)*"); MITK_DEBUG << "matching " << onePart.pattern().toStdString(); int count = 0; int pos = 0; while( (pos = onePart.indexIn( storedString, pos )) != -1 ) { ++count; int length = onePart.matchedLength(); if (length == 0) break; QString matchedString = storedString.mid(pos, length); MITK_DEBUG << " Captured length " << length << ": " << matchedString.toStdString(); pos += length + 1; // skip separating ';' // unescape possible occurrences of '\;' in the string matchedString.replace("\\;", ";"); // add matched string part to output list organColors << matchedString; } MITK_DEBUG << "Captured " << count << " organ name/colors"; } dialog->SetSuggestionList( organColors ); int dialogReturnValue = dialog->exec(); if ( dialogReturnValue == QDialog::Rejected ) return; // user clicked cancel or pressed Esc or something similar // ask the user about an organ type and name, add this information to the image's (!) propertylist // create a new image of the same dimensions and smallest possible pixel type mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); mitk::Tool* firstTool = toolManager->GetToolById(0); if (firstTool) { try { mitk::DataNode::Pointer emptySegmentation = firstTool->CreateEmptySegmentationNode( image, dialog->GetSegmentationName().toStdString(), dialog->GetColor() ); //Here we change the reslice interpolation mode for a segmentation, so that contours in rotated slice can be shown correctly emptySegmentation->SetProperty( "reslice interpolation", mitk::VtkResliceInterpolationProperty::New(VTK_RESLICE_CUBIC) ); // initialize showVolume to false to prevent recalculating the volume while working on the segmentation emptySegmentation->SetProperty( "showVolume", mitk::BoolProperty::New( false ) ); if (!emptySegmentation) return; // could be aborted by user UpdateOrganList( organColors, dialog->GetSegmentationName(), dialog->GetColor() ); /* escape ';' here (replace by '\;'), see longer comment above */ std::string stringForStorage = organColors.replaceInStrings(";","\\;").join(";").toStdString(); MITK_DEBUG << "Will store: " << stringForStorage; this->GetPreferences()->PutByteArray("Organ-Color-List", stringForStorage ); this->GetPreferences()->Flush(); this->GetDefaultDataStorage()->Add( emptySegmentation, node ); // add as a child, because the segmentation "derives" from the original this->FireNodeSelected( emptySegmentation ); this->OnSelectionChanged( emptySegmentation ); } catch (std::bad_alloc) { QMessageBox::warning(NULL,"Create new segmentation","Could not allocate memory for new segmentation"); } } } else { QMessageBox::information(NULL,"Segmentation","Segmentation is currently not supported for 2D images"); } } } else { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a patient image is selected..."; } } void QmitkSegmentationView::CreateSegmentationFromSurface() { mitk::DataNode::Pointer surfaceNode = m_Controls->MaskSurfaces->GetSelectedNode(); mitk::Surface::Pointer surface(0); if(surfaceNode.IsNotNull()) surface = dynamic_cast ( surfaceNode->GetData() ); if(surface.IsNull()) { this->HandleException( "No surface selected.", m_Parent, true); return; } mitk::DataNode::Pointer imageNode = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); mitk::Image::Pointer image(0); if (imageNode.IsNotNull()) image = dynamic_cast( imageNode->GetData() ); if(image.IsNull()) { this->HandleException( "No image selected.", m_Parent, true); return; } mitk::SurfaceToImageFilter::Pointer s2iFilter = mitk::SurfaceToImageFilter::New(); s2iFilter->MakeOutputBinaryOn(); s2iFilter->SetInput(surface); s2iFilter->SetImage(image); s2iFilter->Update(); mitk::DataNode::Pointer resultNode = mitk::DataNode::New(); std::string nameOfResultImage = imageNode->GetName(); nameOfResultImage.append(surfaceNode->GetName()); resultNode->SetProperty("name", mitk::StringProperty::New(nameOfResultImage) ); resultNode->SetProperty("binary", mitk::BoolProperty::New(true) ); resultNode->SetData( s2iFilter->GetOutput() ); this->GetDataStorage()->Add(resultNode, imageNode); } void QmitkSegmentationView::ManualToolSelected(int id) { // disable crosshair movement when a manual drawing tool is active (otherwise too much visual noise) if (m_MultiWidget) { if (id >= 0) { m_MultiWidget->DisableNavigationControllerEventListening(); m_MultiWidget->SetWidgetPlaneMode(0); } else { m_MultiWidget->EnableNavigationControllerEventListening(); } } } void QmitkSegmentationView::ToolboxStackPageChanged(int id) { // interpolation only with manual tools visible m_Controls->m_SlicesInterpolator->EnableInterpolation( id == 0 ); if( id == 0 ) { mitk::DataNode::Pointer workingData = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0); if( workingData.IsNotNull() ) { m_Controls->lblSegmentation->setText( workingData->GetName().c_str() ); m_Controls->lblSegImage->show(); m_Controls->lblSegmentation->show(); } } else { m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } // this is just a workaround, should be removed when all tools support 3D+t if (id==2) // lesions { mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull()) { if (image->GetDimension()>3) { m_Controls->widgetStack->setCurrentIndex(0); QMessageBox::information(NULL,"Segmentation","Lesion segmentation is currently not supported for 4D images"); } } } } } // protected void QmitkSegmentationView::OnComboBoxSelectionChanged( const mitk::DataNode* node ) { mitk::DataNode* selectedNode = const_cast(node); if( selectedNode != NULL ) { m_Controls->refImageSelector->show(); m_Controls->lblReferenceImageSelectionWarning->hide(); this->OnSelectionChanged( const_cast(node) ); } else { m_Controls->refImageSelector->hide(); m_Controls->lblReferenceImageSelectionWarning->show(); } } //to remember the contour positions void QmitkSegmentationView::CheckboxRememberContourPositionsStateChanged (int state) { - if(state == Qt::Checked) - { - m_Controls->m_ManualToolSelectionBox->GetToolManager()->SetRememberContourPosition( true ); - } - else + mitk::SegTool2D::Pointer manualSegmentationTool; + + unsigned int numberOfExistingTools = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetTools().size(); + + for(unsigned int i = 0; i < numberOfExistingTools; i++) + { + manualSegmentationTool = dynamic_cast(m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetToolById(i)); + + if (manualSegmentationTool) { - m_Controls->m_ManualToolSelectionBox->GetToolManager()->SetRememberContourPosition( false ); + if(state == Qt::Checked) + { + manualSegmentationTool->SetRememberContourPositions( true ); + } + else + { + manualSegmentationTool->SetRememberContourPositions( false ); + } } + } } + void QmitkSegmentationView::OnSelectionChanged(mitk::DataNode* node) { std::vector nodes; nodes.push_back( node ); this->OnSelectionChanged( nodes ); } void QmitkSegmentationView::OnSurfaceSelectionChanged() { // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull())) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); } void QmitkSegmentationView::OnSelectionChanged(std::vector nodes) { // if the selected node is a contourmarker if ( !nodes.empty() ) { std::string markerName = "Contourmarker"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at( 0 )->GetName(); if ( ( numberOfNodes == 1 ) && ( nodeName.find( markerName ) == 0) ) { this->OnContourMarkerSelected( nodes.at( 0 ) ); } } // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull())) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); if (!m_Parent || !m_Parent->isVisible()) return; // reaction to BlueBerry selection events // this method will try to figure out if a relevant segmentation and its corresponding original image were selected // a warning is issued if the selection is invalid // appropriate reactions are triggered otherwise mitk::DataNode::Pointer referenceData = FindFirstRegularImage( nodes ); //m_Controls->refImageSelector->GetSelectedNode(); //FindFirstRegularImage( nodes ); mitk::DataNode::Pointer workingData = FindFirstSegmentation( nodes ); bool invalidSelection( !nodes.empty() && ( nodes.size() > 2 || // maximum 2 selected nodes (nodes.size() == 2 && (workingData.IsNull() || referenceData.IsNull()) ) || // with two nodes, one must be the original image, one the segmentation ( workingData.GetPointer() == referenceData.GetPointer() ) //one node is selected as reference and working image // one item is always ok (might be working or reference or nothing ) ); if (invalidSelection) { // TODO visible warning when two images are selected MITK_ERROR << "WARNING: No image, too many (>2) or two equal images were selected."; workingData = NULL; if( m_Controls->refImageSelector->GetSelectedNode().IsNull() ) referenceData = NULL; } if ( workingData.IsNotNull() && referenceData.IsNull() ) { // find the DataStorage parent of workingData // try to find a "normal image" parent, select this as reference image mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer isNotBinary = mitk::NodePredicateNot::New( isBinary ); mitk::NodePredicateAnd::Pointer isNormalImage = mitk::NodePredicateAnd::New( isImage, isNotBinary ); mitk::DataStorage::SetOfObjects::ConstPointer possibleParents = this->GetDefaultDataStorage()->GetSources( workingData, isNormalImage ); if (possibleParents->size() > 0) { if (possibleParents->size() > 1) { // TODO visible warning for this rare case MITK_ERROR << "Selected binary image has multiple parents. Using arbitrary first one for segmentation."; } referenceData = (*possibleParents)[0]; } } //set comboBox to reference image disconnect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); m_Controls->refImageSelector->setCurrentIndex( m_Controls->refImageSelector->Find(referenceData) ); connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); // if Image and Surface are selected, enable button if ( (m_Controls->refImageSelector->GetSelectedNode().IsNull()) || (m_Controls->MaskSurfaces->GetSelectedNode().IsNull()) || (!referenceData)) m_Controls->CreateSegmentationFromSurface->setEnabled(false); else m_Controls->CreateSegmentationFromSurface->setEnabled(true); SetToolManagerSelection(referenceData, workingData); ForceDisplayPreferencesUponAllImages(); } //New since rotated contour drawing is allowed. Effects a reorientation of the plane of the affected widget to the marker`s position void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode *node) { const mitk::PlaneGeometry* markerGeometry = dynamic_cast ( node->GetData()->GetGeometry() ); QmitkRenderWindow* selectedRenderWindow = 0; QmitkRenderWindow* RenderWindow1 = this->GetActiveStdMultiWidget()->GetRenderWindow1(); QmitkRenderWindow* RenderWindow2 = this->GetActiveStdMultiWidget()->GetRenderWindow2(); QmitkRenderWindow* RenderWindow3 = this->GetActiveStdMultiWidget()->GetRenderWindow3(); QmitkRenderWindow* RenderWindow4 = this->GetActiveStdMultiWidget()->GetRenderWindow4(); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow1->GetRenderer())) { selectedRenderWindow = RenderWindow1; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow2->GetRenderer())) { selectedRenderWindow = RenderWindow2; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow3->GetRenderer())) { selectedRenderWindow = RenderWindow3; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, RenderWindow4->GetRenderer())) { selectedRenderWindow = RenderWindow4; } // make node visible if (selectedRenderWindow) { mitk::Point3D centerP = markerGeometry->GetOrigin(); selectedRenderWindow->GetSliceNavigationController()->SelectSliceByPoint( centerP); selectedRenderWindow->GetSliceNavigationController()->ReorientSlices( centerP, markerGeometry->GetNormal()); } } mitk::DataNode::Pointer QmitkSegmentationView::FindFirstRegularImage( std::vector nodes ) { if (nodes.empty()) return NULL; for(unsigned int i = 0; i < nodes.size(); ++i) { //mitk::DataNode::Pointer node = i.value() bool isImage(false); if (nodes.at(i)->GetData()) { isImage = dynamic_cast(nodes.at(i)->GetData()) != NULL; } // make sure this is not a binary image bool isSegmentation(false); nodes.at(i)->GetBoolProperty("binary", isSegmentation); // return first proper mitk::Image if (isImage && !isSegmentation) return nodes.at(i); } return NULL; } mitk::DataNode::Pointer QmitkSegmentationView::FindFirstSegmentation( std::vector nodes ) { if (nodes.empty()) return NULL; for(unsigned int i = 0; i < nodes.size(); ++i) { bool isImage(false); if (nodes.at(i)->GetData()) { isImage = dynamic_cast(nodes.at(i)->GetData()) != NULL; } bool isSegmentation(false); nodes.at(i)->GetBoolProperty("binary", isSegmentation); // return first proper binary mitk::Image if (isImage && isSegmentation) { return nodes.at(i); } } return NULL; } void QmitkSegmentationView::SetToolManagerSelection(const mitk::DataNode* referenceData, const mitk::DataNode* workingData) { // called as a result of new BlueBerry selections // tells the ToolManager for manual segmentation about new selections // updates GUI information about what the user should select mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); toolManager->SetReferenceData(const_cast(referenceData)); toolManager->SetWorkingData( const_cast(workingData)); // check original image m_Controls->btnNewSegmentation->setEnabled(referenceData != NULL); if (referenceData) { m_Controls->lblReferenceImageSelectionWarning->hide(); } else { m_Controls->lblReferenceImageSelectionWarning->show(); m_Controls->lblWorkingImageSelectionWarning->hide(); m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } // check, wheter reference image is aligned like render windows. Otherwise display a visible warning (because 2D tools will probably not work) CheckImageAlignment(); // check segmentation if (referenceData) { if (!workingData) { m_Controls->lblWorkingImageSelectionWarning->show(); if( m_Controls->widgetStack->currentIndex() == 0 ) { m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); } } else { m_Controls->lblWorkingImageSelectionWarning->hide(); if( m_Controls->widgetStack->currentIndex() == 0 ) { m_Controls->lblSegmentation->setText( workingData->GetName().c_str() ); m_Controls->lblSegmentation->show(); m_Controls->lblSegImage->show(); } } } } void QmitkSegmentationView::CheckImageAlignment() { bool wrongAlignment(true); mitk::DataNode::Pointer node = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); if (node.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNotNull() && m_MultiWidget) { wrongAlignment = !( IsRenderWindowAligned(m_MultiWidget->GetRenderWindow1(), image ) && IsRenderWindowAligned(m_MultiWidget->GetRenderWindow2(), image ) && IsRenderWindowAligned(m_MultiWidget->GetRenderWindow3(), image ) ); } if (wrongAlignment) { m_Controls->lblAlignmentWarning->show(); } } } bool QmitkSegmentationView::IsRenderWindowAligned(QmitkRenderWindow* renderWindow, mitk::Image* image) { if (!renderWindow) return false; // for all 2D renderwindows of m_MultiWidget check alignment mitk::PlaneGeometry::ConstPointer displayPlane = dynamic_cast( renderWindow->GetRenderer()->GetCurrentWorldGeometry2D() ); if (displayPlane.IsNull()) return false; int affectedDimension(-1); int affectedSlice(-1); return mitk::SegTool2D::DetermineAffectedImageSlice( image, displayPlane, affectedDimension, affectedSlice ); } void QmitkSegmentationView::ForceDisplayPreferencesUponAllImages() { if (!m_Parent || !m_Parent->isVisible()) return; // check all images and segmentations in DataStorage: // (items in brackets are implicitly done by previous steps) // 1. // if a reference image is selected, // show the reference image // and hide all other images (orignal and segmentation), // (and hide all segmentations of the other original images) // and show all the reference's segmentations // if no reference image is selected, do do nothing // // 2. // if a segmentation is selected, // show it // (and hide all all its siblings (childs of the same parent, incl, NULL parent)) // if no segmentation is selected, do nothing if (!m_Controls) return; // might happen on initialization (preferences loaded) mitk::DataNode::Pointer referenceData = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetReferenceData(0); mitk::DataNode::Pointer workingData = m_Controls->m_ManualToolSelectionBox->GetToolManager()->GetWorkingData(0); // 1. if (referenceData.IsNotNull()) { // iterate all images mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDefaultDataStorage()->GetSubset( isImage ); for ( mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; // apply display preferences ApplyDisplayOptions(node); // set visibility node->SetVisibility(node == referenceData); } } // 2. if (workingData.IsNotNull()) { workingData->SetVisibility(true); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (!node) return; bool isBinary(false); node->GetPropertyValue("binary", isBinary); if (isBinary) { node->SetProperty( "outline binary", mitk::BoolProperty::New( this->GetPreferences()->GetBool("draw outline", true)) ); node->SetProperty( "outline width", mitk::FloatProperty::New( 2.0 ) ); node->SetProperty( "opacity", mitk::FloatProperty::New( this->GetPreferences()->GetBool("draw outline", true) ? 1.0 : 0.3 ) ); node->SetProperty( "volumerendering", mitk::BoolProperty::New( this->GetPreferences()->GetBool("volume rendering", false) ) ); } } void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->lblWorkingImageSelectionWarning->hide(); m_Controls->lblAlignmentWarning->hide(); m_Controls->lblSegImage->hide(); m_Controls->lblSegmentation->hide(); m_Controls->refImageSelector->SetDataStorage(this->GetDefaultDataStorage()); m_Controls->refImageSelector->SetPredicate(mitk::NodePredicateDataType::New("Image")); if( m_Controls->refImageSelector->GetSelectedNode().IsNotNull() ) m_Controls->lblReferenceImageSelectionWarning->hide(); else m_Controls->refImageSelector->hide(); mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); toolManager->SetDataStorage( *(this->GetDefaultDataStorage()) ); assert ( toolManager ); // all part of open source MITK m_Controls->m_ManualToolSelectionBox->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox->SetToolGUIArea( m_Controls->m_ManualToolGUIContainer ); m_Controls->m_ManualToolSelectionBox->SetDisplayedToolGroups("Add Subtract Paint Wipe 'Region Growing' Correction Fill Erase"); m_Controls->m_ManualToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingData ); // available only in the 3M application if ( !m_Controls->m_OrganToolSelectionBox->children().count() ) { m_Controls->widgetStack->setItemEnabled( 1, false ); } m_Controls->m_OrganToolSelectionBox->SetToolManager( *toolManager ); m_Controls->m_OrganToolSelectionBox->SetToolGUIArea( m_Controls->m_OrganToolGUIContainer ); m_Controls->m_OrganToolSelectionBox->SetDisplayedToolGroups("'Hippocampus left' 'Hippocampus right' 'Lung left' 'Lung right' 'Liver' 'Heart LV' 'Endocard LV' 'Epicard LV' 'Prostate'"); m_Controls->m_OrganToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceData ); // available only in the 3M application if ( !m_Controls->m_LesionToolSelectionBox->children().count() ) { m_Controls->widgetStack->setItemEnabled( 2, false ); } m_Controls->m_LesionToolSelectionBox->SetToolManager( *toolManager ); m_Controls->m_LesionToolSelectionBox->SetToolGUIArea( m_Controls->m_LesionToolGUIContainer ); m_Controls->m_LesionToolSelectionBox->SetDisplayedToolGroups("'Lymph Node'"); m_Controls->m_LesionToolSelectionBox->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceData ); toolManager->NewNodesGenerated += mitk::MessageDelegate( this, &QmitkSegmentationView::NewNodesGenerated ); // update the list of segmentations toolManager->NewNodeObjectsGenerated += mitk::MessageDelegate1( this, &QmitkSegmentationView::NewNodeObjectsGenerated ); // update the list of segmentations // create signal/slot connections connect( m_Controls->refImageSelector, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnComboBoxSelectionChanged( const mitk::DataNode* ) ) ); connect( m_Controls->btnNewSegmentation, SIGNAL(clicked()), this, SLOT(CreateNewSegmentation()) ); connect( m_Controls->CreateSegmentationFromSurface, SIGNAL(clicked()), this, SLOT(CreateSegmentationFromSurface()) ); connect( m_Controls->m_ManualToolSelectionBox, SIGNAL(ToolSelected(int)), this, SLOT(ManualToolSelected(int)) ); connect( m_Controls->widgetStack, SIGNAL(currentChanged(int)), this, SLOT(ToolboxStackPageChanged(int)) ); //To remember the position of each contour connect( m_Controls->cbRememberContourPositions, SIGNAL(stateChanged(int)), this, SLOT(CheckboxRememberContourPositionsStateChanged(int))); connect(m_Controls->MaskSurfaces, SIGNAL( OnSelectionChanged( const mitk::DataNode* ) ), this, SLOT( OnSurfaceSelectionChanged( ) ) ); m_Controls->MaskSurfaces->SetDataStorage(this->GetDefaultDataStorage()); m_Controls->MaskSurfaces->SetPredicate(mitk::NodePredicateDataType::New("Surface")); //// create helper class to provide context menus for segmentations in data manager // m_PostProcessing = new QmitkSegmentationPostProcessing(this->GetDefaultDataStorage(), this, m_Parent); } //void QmitkSegmentationView::OnPlaneModeChanged(int i) //{ // //if plane mode changes, disable all tools // if (m_MultiWidget) // { // mitk::ToolManager* toolManager = m_Controls->m_ManualToolSelectionBox->GetToolManager(); // // if (toolManager) // { // if (toolManager->GetActiveToolID() >= 0) // { // toolManager->ActivateTool(-1); // } // else // { // m_MultiWidget->EnableNavigationControllerEventListening(); // } // } // } //} // ATTENTION some methods for handling the known list of (organ names, colors) are defined in QmitkSegmentationOrganNamesHandling.cpp diff --git a/Modules/MitkExt/Controllers/mitkToolManager.cpp b/Modules/MitkExt/Controllers/mitkToolManager.cpp index 2d1907e8f9..67b277ae68 100644 --- a/Modules/MitkExt/Controllers/mitkToolManager.cpp +++ b/Modules/MitkExt/Controllers/mitkToolManager.cpp @@ -1,494 +1,493 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkToolManager.h" #include "mitkGlobalInteraction.h" #include "mitkCoreObjectFactory.h" #include #include #include mitk::ToolManager::ToolManager(DataStorage* storage) :m_ActiveTool(NULL), m_ActiveToolID(-1), m_RegisteredClients(0), - m_DataStorage(storage), - m_RememberContourPosition (false) + m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools are registered) - bug 1029 // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for ( std::list::iterator iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter ) { if ( Tool* tool = dynamic_cast( iter->GetPointer() ) ) { tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1( this, &ToolManager::OnToolErrorMessage ); tool->GeneralMessage += MessageDelegate1( this, &ToolManager::OnGeneralToolMessage ); m_Tools.push_back( tool ); } } //ActivateTool(0); // first one is default } mitk::ToolManager::~ToolManager() { if (m_ActiveTool) { m_ActiveTool->Deactivated(); GlobalInteraction::GetInstance()->RemoveListener( m_ActiveTool ); m_ActiveTool = NULL; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for ( NodeTagMapType::iterator observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter ) { observerTagMapIter->first->RemoveObserver( observerTagMapIter->second ); } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for ( ToolVectorType::iterator iter = m_Tools.begin(); iter != m_Tools.end(); ++iter ) { resultList.push_back( iter->GetPointer() ); } return resultList; } mitk::Tool* mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch(std::exception&) { return NULL; } } bool mitk::ToolManager::ActivateTool(int id) { //MITK_INFO << "ToolManager::ActivateTool("<Deactivated(); GlobalInteraction::GetInstance()->RemoveListener( m_ActiveTool ); } m_ActiveTool = GetToolById( nextTool ); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); GlobalInteraction::GetInstance()->AddListener( m_ActiveTool ); } } } inActivateTool = false; return (m_ActiveTool != NULL); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for ( DataVectorType::iterator dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter ) { NodeTagMapType::iterator searchIter = m_ReferenceDataObserverTags.find( *dataIter ); if ( searchIter != m_ReferenceDataObserverTags.end() ) { //MITK_INFO << "Stopping observation of " << (void*)(*dataIter) << std::endl; (*dataIter)->RemoveObserver( searchIter->second ); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for ( DataVectorType::iterator dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter ) { //MITK_INFO << "Observing " << (void*)(*dataIter) << std::endl; itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheReferenceDataDeleted ); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheReferenceDataDeletedConst ); m_ReferenceDataObserverTags.insert( std::pair( (*dataIter), (*dataIter)->AddObserver( itk::DeleteEvent(), command ) ) ); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object* caller, const itk::EventObject& e) { OnOneOfTheReferenceDataDeleted( const_cast(caller), e ); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object* caller, const itk::EventObject& itkNotUsed(e)) { //MITK_INFO << "Deleted: " << (void*)caller << " Removing from reference data list." << std::endl; DataVectorType v; for (DataVectorType::iterator dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter ) { //MITK_INFO << " In list: " << (void*)(*dataIter); if ( (void*)(*dataIter) != (void*)caller ) { v.push_back( *dataIter ); //MITK_INFO << " kept" << std::endl; } else { //MITK_INFO << " removed" << std::endl; m_ReferenceDataObserverTags.erase( *dataIter ); // no tag to remove anymore } } this->SetReferenceData( v ); } void mitk::ToolManager::SetReferenceData(DataNode* data) { //MITK_INFO << "ToolManager::SetReferenceData(" << (void*)data << ")" << std::endl; DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if ( data != m_WorkingData ) { // remove observers from old nodes for ( DataVectorType::iterator dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter ) { NodeTagMapType::iterator searchIter = m_WorkingDataObserverTags.find( *dataIter ); if ( searchIter != m_WorkingDataObserverTags.end() ) { //MITK_INFO << "Stopping observation of " << (void*)(*dataIter) << std::endl; (*dataIter)->RemoveObserver( searchIter->second ); } } m_WorkingData = data; // TODO tell active tool? // attach new observers m_WorkingDataObserverTags.clear(); for ( DataVectorType::iterator dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter ) { //MITK_INFO << "Observing " << (void*)(*dataIter) << std::endl; itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheWorkingDataDeleted ); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheWorkingDataDeletedConst ); m_WorkingDataObserverTags.insert( std::pair( (*dataIter), (*dataIter)->AddObserver( itk::DeleteEvent(), command ) ) ); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object* caller, const itk::EventObject& e) { OnOneOfTheWorkingDataDeleted( const_cast(caller), e ); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object* caller, const itk::EventObject& itkNotUsed(e)) { //MITK_INFO << "Deleted: " << (void*)caller << " Removing from reference data list." << std::endl; DataVectorType v; for (DataVectorType::iterator dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter ) { //MITK_INFO << " In list: " << (void*)(*dataIter); if ( (void*)(*dataIter) != (void*)caller ) { v.push_back( *dataIter ); //MITK_INFO << " kept" << std::endl; } else { //MITK_INFO << " removed" << std::endl; m_WorkingDataObserverTags.erase( *dataIter ); // no tag to remove anymore } } this->SetWorkingData( v ); } void mitk::ToolManager::SetWorkingData(DataNode* data) { DataVectorType v; if (data) // don't allow for NULL nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for ( DataVectorType::iterator dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter ) { NodeTagMapType::iterator searchIter = m_RoiDataObserverTags.find( *dataIter ); if ( searchIter != m_RoiDataObserverTags.end() ) { //MITK_INFO << "Stopping observation of " << (void*)(*dataIter) << std::endl; (*dataIter)->RemoveObserver( searchIter->second ); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for ( DataVectorType::iterator dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter ) { //MITK_INFO << "Observing " << (void*)(*dataIter) << std::endl; itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheRoiDataDeleted ); command->SetCallbackFunction( this, &ToolManager::OnOneOfTheRoiDataDeletedConst ); m_RoiDataObserverTags.insert( std::pair( (*dataIter), (*dataIter)->AddObserver( itk::DeleteEvent(), command ) ) ); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode* data) { DataVectorType v; if(data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object* caller, const itk::EventObject& e) { OnOneOfTheRoiDataDeleted( const_cast(caller), e ); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object* caller, const itk::EventObject& itkNotUsed(e)) { //MITK_INFO << "Deleted: " << (void*)caller << " Removing from roi data list." << std::endl; DataVectorType v; for (DataVectorType::iterator dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter ) { //MITK_INFO << " In list: " << (void*)(*dataIter); if ( (void*)(*dataIter) != (void*)caller ) { v.push_back( *dataIter ); //MITK_INFO << " kept" << std::endl; } else { //MITK_INFO << " removed" << std::endl; m_RoiDataObserverTags.erase( *dataIter ); // no tag to remove anymore } } this->SetRoiData( v ); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode* mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch(std::exception&) { return NULL; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode* mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch(std::exception&) { return NULL; } } mitk::DataStorage* mitk::ToolManager::GetDataStorage() { if ( m_DataStorage.IsNotNull() ) { return m_DataStorage; } else { return NULL; } } void mitk::ToolManager::SetDataStorage(DataStorage& storage) { m_DataStorage = &storage; } mitk::DataNode* mitk::ToolManager::GetWorkingData(int idx) { try { return m_WorkingData.at(idx); } catch(std::exception&) { return NULL; } } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool* mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if ( m_RegisteredClients < 1 ) { if ( m_ActiveTool ) { m_ActiveTool->Activated(); GlobalInteraction::GetInstance()->AddListener( m_ActiveTool ); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if ( m_RegisteredClients < 1) return; --m_RegisteredClients; if ( m_RegisteredClients < 1 ) { if ( m_ActiveTool ) { m_ActiveTool->Deactivated(); GlobalInteraction::GetInstance()->RemoveListener( m_ActiveTool ); } } } int mitk::ToolManager::GetToolID( const Tool* tool ) { int id(0); for ( ToolVectorType::iterator iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id ) { if ( tool == iter->GetPointer() ) { return id; } } return -1; } diff --git a/Modules/MitkExt/Controllers/mitkToolManager.h b/Modules/MitkExt/Controllers/mitkToolManager.h index dce5fb9063..7153366576 100644 --- a/Modules/MitkExt/Controllers/mitkToolManager.h +++ b/Modules/MitkExt/Controllers/mitkToolManager.h @@ -1,292 +1,285 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef mitkToolManager_h_Included #define mitkToolManager_h_Included #include "mitkTool.h" #include "MitkExtExports.h" #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkWeakPointer.h" #include #include #include namespace mitk { class Image; class PlaneGeometry; /** \brief Manages and coordinates instances of mitk::Tool. \sa QmitkToolSelectionBox \sa QmitkToolReferenceDataSelectionBox \sa QmitkToolWorkingDataSelectionBox \sa Tool \sa QmitkInteractiveSegmentation \ingroup Interaction \ingroup ToolManagerEtAl There is a separate page describing the general design of QmitkInteractiveSegmentation: \ref QmitkInteractiveSegmentationTechnicalPage This class creates and manages several instances of mitk::Tool. \li ToolManager creates instances of mitk::Tool by asking the itk::ObjectFactory to list all known implementations of mitk::Tool. As a result, one has to implement both a subclass of mitk::Tool and a matching subclass of itk::ObjectFactoryBase that is registered to the top-level itk::ObjectFactory. For an example, see mitkContourToolFactory.h. (this limitiation of one-class-one-factory is due to the implementation of itk::ObjectFactory). In MITK, the right place to register the factories to itk::ObjectFactory is the mitk::QMCoreObjectFactory or mitk::SBCoreObjectFactory. \li One (and only one - or none at all) of the registered tools can be activated using ActivateTool. This tool is registered to mitk::GlobalInteraction as a listener and will receive all mouse clicks and keyboard strokes that get into the MITK event mechanism. Tools are automatically unregistered from GlobalInteraction when no clients are registered to ToolManager (see RegisterClient()). \li ToolManager knows a set of "reference" DataNodes and a set of "working" DataNodes. The first application are segmentation tools, where the reference is the original image and the working data the (kind of) binary segmentation. However, ToolManager is implemented more generally, so that there could be other tools that work, e.g., with surfaces. \li Any "user/client" of ToolManager, i.e. every functionality that wants to use a tool, should call RegisterClient when the tools should be active. ToolManager keeps track of how many clients want it to be used, and when this count reaches zero, it unregistes the active Tool from GlobalInteraction. In "normal" settings, the functionality does not need to care about that if it uses a QmitkToolSelectionBox, which does exactly that when it is enabled/disabled. \li There is a set of events that are sent by ToolManager. At the moment these are TODO update documentation: - mitk::ToolReferenceDataChangedEvent whenever somebody calls SetReferenceData. Most of the time this actually means that the data has changed, but there might be cases where the same data is passed to SetReferenceData a second time, so don't rely on the assumption that something actually changed. - mitk::ToolSelectedEvent is sent when a (truly) different tool was activated. In reaction to this event you can ask for the active Tool using GetActiveTool or GetActiveToolID (where NULL or -1 indicate that NO tool is active at the moment). Design descisions: \li Not a singleton, because there could be two functionalities using tools, each one with different reference/working data. $Author$ */ class MitkExt_EXPORT ToolManager : public itk::Object { public: typedef std::vector ToolVectorType; typedef std::vector ToolVectorTypeConst; typedef std::vector DataVectorType; // has to be observed for delete events! typedef std::map NodeTagMapType; Message<> NodePropertiesChanged; Message<> NewNodesGenerated; Message1 NewNodeObjectsGenerated; Message<> ActiveToolChanged; Message<> ReferenceDataChanged; Message<> WorkingDataChanged; Message<> RoiDataChanged; Message1 ToolErrorMessage; Message1 GeneralToolMessage; mitkClassMacro(ToolManager, itk::Object); mitkNewMacro1Param(ToolManager, DataStorage*); - itkSetMacro(RememberContourPosition, bool); - - itkGetMacro(RememberContourPosition, bool); - /** \brief Gives you a list of all tools. This is const on purpose. */ const ToolVectorTypeConst GetTools(); int GetToolID( const Tool* tool ); /* \param id The tool of interest. Counting starts with 0. */ Tool* GetToolById(int id); /** \param id The tool to activate. Provide -1 for disabling any tools. Counting starts with 0. */ bool ActivateTool(int id); template int GetToolIdByToolType() { int id = 0; for ( ToolVectorType::iterator iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id ) { if ( dynamic_cast(iter->GetPointer()) ) { return id; } } return -1; } /** \return -1 for "No tool is active" */ int GetActiveToolID(); /** \return NULL for "No tool is active" */ Tool* GetActiveTool(); /* \brief Set a list of data/images as reference objects. */ void SetReferenceData(DataVectorType); /* \brief Set single data item/image as reference object. */ void SetReferenceData(DataNode*); /* \brief Set a list of data/images as working objects. */ void SetWorkingData(DataVectorType); /* \brief Set single data item/image as working object. */ void SetWorkingData(DataNode*); /* \brief Set a list of data/images as roi objects. */ void SetRoiData(DataVectorType); /* \brief Set a single data item/image as roi object. */ void SetRoiData(DataNode*); /* \brief Get the list of reference data. */ DataVectorType GetReferenceData(); /* \brief Get the current reference data. \warning If there is a list of items, this method will only return the first list item. */ DataNode* GetReferenceData(int); /* \brief Get the list of working data. */ DataVectorType GetWorkingData(); /* \brief Get the current working data. \warning If there is a list of items, this method will only return the first list item. */ DataNode* GetWorkingData(int); /* \brief Get the current roi data */ DataVectorType GetRoiData(); /* \brief Get the roi data at position idx */ DataNode* GetRoiData(int idx); DataStorage* GetDataStorage(); void SetDataStorage(DataStorage& storage); /* \brief Tell that someone is using tools. GUI elements should call this when they become active. This method increases an internal "client count". Tools are only registered to GlobalInteraction when this count is greater than 0. This is useful to automatically deactivate tools when you hide their GUI elements. */ void RegisterClient(); /* \brief Tell that someone is NOT using tools. GUI elements should call this when they become active. This method increases an internal "client count". Tools are only registered to GlobalInteraction when this count is greater than 0. This is useful to automatically deactivate tools when you hide their GUI elements. */ void UnregisterClient(); void OnOneOfTheReferenceDataDeletedConst(const itk::Object* caller, const itk::EventObject& e); void OnOneOfTheReferenceDataDeleted (itk::Object* caller, const itk::EventObject& e); void OnOneOfTheWorkingDataDeletedConst(const itk::Object* caller, const itk::EventObject& e); void OnOneOfTheWorkingDataDeleted (itk::Object* caller, const itk::EventObject& e); void OnOneOfTheRoiDataDeletedConst(const itk::Object* caller, const itk::EventObject& e); void OnOneOfTheRoiDataDeleted (itk::Object* caller, const itk::EventObject& e); /* \brief Connected to tool's messages This method just resends error messages coming from any of the tools. This way clients (GUIs) only have to observe one message. */ void OnToolErrorMessage(std::string s); void OnGeneralToolMessage(std::string s); protected: /** You may specify a list of tool "groups" that should be available for this ToolManager. Every Tool can report its group as a string. This constructor will try to find the tool's group inside the supplied string. If there is a match, the tool is accepted. Effectively, you can provide a human readable list like "default, lymphnodevolumetry, oldERISstuff". */ ToolManager(DataStorage* storage); // purposely hidden virtual ~ToolManager(); ToolVectorType m_Tools; Tool* m_ActiveTool; int m_ActiveToolID; DataVectorType m_ReferenceData; NodeTagMapType m_ReferenceDataObserverTags; DataVectorType m_WorkingData; NodeTagMapType m_WorkingDataObserverTags; DataVectorType m_RoiData; NodeTagMapType m_RoiDataObserverTags; int m_RegisteredClients; WeakPointer m_DataStorage; - - //if the position of each contour shall be saved. Therefor for each contour a markernode is added to the datastorage - bool m_RememberContourPosition; }; } // namespace #endif diff --git a/Modules/MitkExt/Interactions/mitkContourTool.cpp b/Modules/MitkExt/Interactions/mitkContourTool.cpp index ac0b7192a5..e3d1ad5f0a 100644 --- a/Modules/MitkExt/Interactions/mitkContourTool.cpp +++ b/Modules/MitkExt/Interactions/mitkContourTool.cpp @@ -1,193 +1,193 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #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) { } 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; 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; 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; 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; int affectedDimension( -1 ); int affectedSlice( -1 ); SegTool2D::DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ); // 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 ); // 3. Write the modified 2D working data slice back into the image if (affectedDimension != -1) { OverwriteSliceImageFilter::Pointer slicewriter = OverwriteSliceImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( true ); slicewriter->SetSliceImage( slice ); slicewriter->SetSliceDimension( affectedDimension ); slicewriter->SetSliceIndex( affectedSlice ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions && m_PaintingPixelValue == 1) { this->AddContourmarker(positionEvent); } } else { OverwriteDirectedPlaneImageFilter::Pointer slicewriter = OverwriteDirectedPlaneImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( false ); slicewriter->SetSliceImage( slice ); slicewriter->SetPlaneGeometry3D( slice->GetGeometry() ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions && m_PaintingPixelValue == 1) { this->AddContourmarker(positionEvent); } } // 4. Make sure the result is drawn again --> is visible then. assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( 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; // 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/MitkExt/Interactions/mitkCorrectorTool2D.cpp b/Modules/MitkExt/Interactions/mitkCorrectorTool2D.cpp index 8018b5e464..89910a85a4 100644 --- a/Modules/MitkExt/Interactions/mitkCorrectorTool2D.cpp +++ b/Modules/MitkExt/Interactions/mitkCorrectorTool2D.cpp @@ -1,176 +1,186 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #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(MitkExt_EXPORT, CorrectorTool2D, "Correction tool"); } mitk::CorrectorTool2D::CorrectorTool2D(int paintingPixelValue) :FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) { 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; 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; 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; 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; int affectedDimension( -1 ); int affectedSlice( -1 ); FeedbackContourTool::DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ); // 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; } if ( affectedDimension != -1 ) { // 5. Write the modified 2D working data slice back into the image OverwriteSliceImageFilter::Pointer slicewriter = OverwriteSliceImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( true ); slicewriter->SetSliceImage( algorithm->GetOutput() ); slicewriter->SetSliceDimension( affectedDimension ); slicewriter->SetSliceIndex( affectedSlice ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); + + if( m_RememberContourPositions ) + { + this->AddContourmarker( positionEvent ); + } } else if ( affectedDimension == -1 ) { OverwriteDirectedPlaneImageFilter::Pointer slicewriter = OverwriteDirectedPlaneImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( false ); slicewriter->SetSliceImage( m_WorkingSlice ); slicewriter->SetPlaneGeometry3D( m_WorkingSlice->GetGeometry() ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); + + if( m_RememberContourPositions ) + { + this->AddContourmarker( positionEvent ); + } } else { InteractiveSegmentationBugMessage( "FeedbackContourTool could not determine which slice of the image you are drawing on." ); } // 6. Make sure the result is drawn again --> is visible then. assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } diff --git a/Modules/MitkExt/Interactions/mitkPaintbrushTool.cpp b/Modules/MitkExt/Interactions/mitkPaintbrushTool.cpp index 8f3fe8e8b9..4cce106949 100644 --- a/Modules/MitkExt/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/MitkExt/Interactions/mitkPaintbrushTool.cpp @@ -1,353 +1,358 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision: 1.12 $ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #include "mitkPaintbrushTool.h" #include "mitkToolManager.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkBaseRenderer.h" #include "mitkImageDataItem.h" #include "ipSegmentation.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) { m_MasterContour = Contour::New(); m_MasterContour->Initialize(); } mitk::PaintbrushTool::~PaintbrushTool() { } void mitk::PaintbrushTool::Activated() { Superclass::Activated(); FeedbackContourTool::SetFeedbackContourVisible(true); SizeChanged.Send(m_Size); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); Superclass::Deactivated(); } void mitk::PaintbrushTool::SetSize(int value) { m_Size = value; } void mitk::PaintbrushTool::UpdateContour(const StateEvent* stateEvent) { // 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; Image::Pointer m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice( positionEvent ); if (m_WorkingSlice.IsNull()) 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, 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() ); 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; 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) { if (FeedbackContourTool::OnMousePressed( action, stateEvent )) { const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (positionEvent) { UpdateContour( stateEvent ); } } return this->OnMouseMoved(action, stateEvent); /* return true; */ } /** 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) { bool leftMouseButtonPressed( stateEvent->GetId() == 530 || stateEvent->GetId() == 1 || stateEvent->GetId() == 5 ); const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return false; if ( m_LastContourSize != m_Size ) { UpdateContour( stateEvent ); m_LastContourSize = m_Size; } 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; int affectedDimension( -1 ); int affectedSlice( -1 ); if ( !SegTool2D::DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ) ) return false; Point3D worldCoordinates = positionEvent->GetWorldPosition(); Point3D indexCoordinates; image->GetGeometry()->WorldToIndex( worldCoordinates, indexCoordinates ); MITK_DEBUG << "Mouse at W " << worldCoordinates << std::endl; MITK_DEBUG << "Mouse at I " << indexCoordinates << std::endl; unsigned int firstDimension(0); unsigned int secondDimension(1); switch( affectedDimension ) { case 2: // transversal default: firstDimension = 0; secondDimension = 1; break; case 1: // frontal firstDimension = 0; secondDimension = 2; break; case 0: // sagittal firstDimension = 1; secondDimension = 2; break; } // round to nearest voxel center (abort if this hasn't changed) if ( m_Size % 2 == 0 ) // even { indexCoordinates[firstDimension] = ROUND( indexCoordinates[firstDimension] + 0.5); indexCoordinates[secondDimension] = ROUND( indexCoordinates[secondDimension] + 0.5 ); } else // odd { indexCoordinates[firstDimension] = ROUND( indexCoordinates[firstDimension] ) ; indexCoordinates[secondDimension] = ROUND( indexCoordinates[secondDimension] ) ; } static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me static bool lastLeftMouseButtonPressed(false); if ( fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps || fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed != lastLeftMouseButtonPressed ) { lastPos = indexCoordinates; lastLeftMouseButtonPressed = leftMouseButtonPressed; } 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[ firstDimension ]; point[1] += indexCoordinates[ secondDimension ]; MITK_DEBUG << "Contour point [" << index << "] :" << point; contour->AddVertex( point ); } Image::Pointer slice = SegTool2D::GetAffectedImageSliceAs2DImage( positionEvent, image ); if ( slice.IsNull() ) return false; if (leftMouseButtonPressed) { FeedbackContourTool::FillContourInSlice( contour, slice, m_PaintingPixelValue ); OverwriteSliceImageFilter::Pointer slicewriter = OverwriteSliceImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( true ); slicewriter->SetSliceImage( slice ); slicewriter->SetSliceDimension( affectedDimension ); slicewriter->SetSliceIndex( affectedSlice ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); + + if ( m_RememberContourPositions && m_PaintingPixelValue == 1) + { + this->AddContourmarker(positionEvent); + } } // 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( slice->GetGeometry(), displayContour ); SetFeedbackContour( *displayContour ); assert( positionEvent->GetSender()->GetRenderWindow() ); RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); return true; } bool mitk::PaintbrushTool::OnMouseReleased(Action* /*action*/, const StateEvent* /*stateEvent*/) { //FeedbackContourTool::SetFeedbackContourVisible(false); 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* action, const StateEvent* stateEvent) { if (!FeedbackContourTool::OnInvertLogic(action, stateEvent)) 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/MitkExt/Interactions/mitkRegionGrowingTool.cpp b/Modules/MitkExt/Interactions/mitkRegionGrowingTool.cpp index 3c098bb5a0..02afda10ef 100644 --- a/Modules/MitkExt/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/MitkExt/Interactions/mitkRegionGrowingTool.cpp @@ -1,712 +1,712 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #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(MitkExt_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(3.0), m_LastWorkingSeed(-1), m_FillFeedbackContour(true) { } 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) { //ToolLogger::SetVerboseness(3); MITK_INFO << "OnMousePressed" << std::endl; if (FeedbackContourTool::OnMousePressed( action, stateEvent )) { MITK_INFO << "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_INFO << "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_INFO << "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_INFO << "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 = temporarySlice->GetSliceData()->GetPicDescriptor(); 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_INFO << "OnMousePressed: got reference slice" << std::endl; m_OriginalPicSlice = m_ReferenceSlice->GetSliceData()->GetPicDescriptor(); // 3.1. Switch depending on the pixel value if (inside) { OnMousePressedInside(action, stateEvent, workingPicSlice, initialWorkingOffset); } else { OnMousePressedOutside(action, stateEvent); } } } } } } } MITK_INFO << "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_INFO << "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. m_VisibleWindow = lw.GetWindow(); // necessary for limiting the upper and lower threshold to the maximum gray values m_DefaultWindow = lw.GetDefaultWindow(); static bool initializedAlready = false; // just evaluated once if (!initializedAlready) { m_InitialLowerThreshold = static_cast(m_VisibleWindow / 10.0); // 20% of the visible gray values m_InitialUpperThreshold = static_cast(m_VisibleWindow / 10.0); initializedAlready = true; } m_LowerThreshold = m_InitialLowerThreshold; m_UpperThreshold = m_InitialUpperThreshold; DisplayGeometry* displayGeometry = positionEvent->GetSender()->GetDisplayGeometry(); if (displayGeometry) { m_MouseDistanceScaleFactor = m_VisibleWindow / ( 3.0 * displayGeometry->GetDisplayHeight() ); } // 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; } /** 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 ( 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 = m_InitialLowerThreshold + static_cast( m_ScreenYDifference * m_MouseDistanceScaleFactor ); if (m_LowerThreshold < 1) m_LowerThreshold = 1; if (m_LowerThreshold > m_VisibleWindow / 2) m_LowerThreshold = m_VisibleWindow / 2; m_UpperThreshold = m_InitialUpperThreshold + static_cast( m_ScreenYDifference * m_MouseDistanceScaleFactor ); if (m_UpperThreshold < 1) m_UpperThreshold = 1; if (m_UpperThreshold > m_VisibleWindow / 2) m_UpperThreshold = m_VisibleWindow / 2; //MITK_INFO << "new interval: l " << m_LowerThreshold << " u " << m_UpperThreshold << std::endl; // 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 )) { // 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 ); // 4. write working slice back into image volume int affectedDimension( -1 ); int affectedSlice( -1 ); const PlaneGeometry* planeGeometry( dynamic_cast (positionEvent->GetSender()->GetCurrentWorldGeometry2D() ) ); FeedbackContourTool::DetermineAffectedImageSlice( dynamic_cast( m_ToolManager->GetWorkingData(0)->GetData() ), planeGeometry, affectedDimension, affectedSlice ); MITK_INFO << "OnMouseReleased: writing back to dimension " << affectedDimension << ", slice " << affectedSlice << " in working image" << std::endl; //If dazu gemacht Image::Pointer workingImage = dynamic_cast( m_ToolManager->GetWorkingData(0)->GetData() ); if ( affectedDimension != -1 ) { OverwriteSliceImageFilter::Pointer slicewriter = OverwriteSliceImageFilter::New(); slicewriter->SetInput( workingImage ); slicewriter->SetCreateUndoInformation( true ); slicewriter->SetSliceImage( m_WorkingSlice ); slicewriter->SetSliceDimension( affectedDimension ); slicewriter->SetSliceIndex( affectedSlice ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( workingImage ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions ) { this->AddContourmarker(positionEvent); } } else { OverwriteDirectedPlaneImageFilter::Pointer slicewriter = OverwriteDirectedPlaneImageFilter::New(); slicewriter->SetInput( workingImage ); slicewriter->SetCreateUndoInformation( false ); slicewriter->SetSliceImage( m_WorkingSlice ); slicewriter->SetPlaneGeometry3D( m_WorkingSlice->GetGeometry() ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( workingImage ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions ) { this->AddContourmarker(positionEvent); } } } } } 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/MitkExt/Interactions/mitkSegTool2D.cpp b/Modules/MitkExt/Interactions/mitkSegTool2D.cpp index 0d8f40e789..28a485054e 100644 --- a/Modules/MitkExt/Interactions/mitkSegTool2D.cpp +++ b/Modules/MitkExt/Interactions/mitkSegTool2D.cpp @@ -1,326 +1,325 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #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" #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 ("Contourmarker") + m_Contourmarkername ("Contourmarker"), + m_RememberContourPositions (false) { // 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) { 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; } bool mitk::SegTool2D::OnMouseMoved (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::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; int affectedDimension( -1 ); int affectedSlice( -1 ); DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ); if ( DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ) ) { try { // now we extract the correct slice from the volume, resulting in a 2D image ExtractImageFilter::Pointer extractor= ExtractImageFilter::New(); extractor->SetInput( image ); extractor->SetSliceDimension( affectedDimension ); extractor->SetSliceIndex( affectedSlice ); extractor->SetTimeStep( timeStep ); extractor->Update(); // here we have a single slice that can be modified Image::Pointer slice = extractor->GetOutput(); return slice; } catch(...) { // not working return NULL; } } else { ExtractDirectedPlaneImageFilterNew::Pointer newExtractor = ExtractDirectedPlaneImageFilterNew::New(); newExtractor->SetInput( image ); newExtractor->SetActualInputTimestep( timeStep ); newExtractor->SetCurrentWorldGeometry2D( planeGeometry ); newExtractor->Update(); Image::Pointer slice = newExtractor->GetOutput(); 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::AddContourmarker ( const PositionEvent* positionEvent ) { - char* subtractToolIsActive = strstr( const_cast(m_ToolManager->GetActiveTool()->GetName()), "Subtract" ); mitk::PlaneGeometry* currentGeometry2D = dynamic_cast( const_cast(positionEvent->GetSender()->GetCurrentWorldGeometry2D())); - if ( ( currentGeometry2D != NULL ) && ( !ContourmarkerAlreadyExists( currentGeometry2D ) && subtractToolIsActive == NULL ) ) + if ( ( currentGeometry2D != NULL ) && ( !ContourmarkerAlreadyExists( currentGeometry2D )) ) { //Creating PlanarFigure which serves as marker mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); contourMarker->SetGeometry2D( currentGeometry2D ); //Here we check which consecutive number must be the suffix to the markers name std::stringstream markerStream; markerStream << m_Contourmarkername; int markerCount = 0; mitk::DataNode* workingNode (m_ToolManager->GetWorkingData(0)); while (m_ToolManager->GetDataStorage()->GetNamedDerivedNode(markerStream.str().c_str(), workingNode)) { m_ToolManager->GetDataStorage()->GetNamedDerivedNode(markerStream.str().c_str(),workingNode)->SetProperty( "visible", mitk::BoolProperty::New(false) ); markerCount++; markerStream.str(""); markerStream.clear(); markerStream << m_Contourmarkername ; markerStream << " "; markerStream << markerCount; } //Now we place the figure in the image and the DataManager as a new Node Point2D controlPoint2D; Point3D controlPoint3D = positionEvent->GetWorldPosition(); currentGeometry2D->Map(controlPoint3D ,controlPoint2D); - contourMarker->PlaceFigure(controlPoint2D); DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty( "name", StringProperty::New(markerStream.str()) ); rotatedContourNode->SetBoolProperty( "PlanarFigureInitializedWindow", true, positionEvent->GetSender() ); rotatedContourNode->SetProperty( "includeInBoundingBox", BoolProperty::New(false)); m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } } bool mitk::SegTool2D::ContourmarkerAlreadyExists ( const mitk::PlaneGeometry* currentGeometry2D ) { itk::VectorContainer::ConstPointer allNodes = m_ToolManager->GetDataStorage()->GetDerivations( m_ToolManager->GetWorkingData(0) ); mitk::DataNode* currentNode; for( unsigned int i = 0; i < allNodes->Size(); i++ ) { currentNode = allNodes->GetElement(i); std::string nodeName = currentNode->GetName(); const mitk::PlaneGeometry* nodeGeometry = dynamic_cast( currentNode->GetData()->GetGeometry() ); if ( !nodeGeometry ) continue; Point3D nodeCenter = nodeGeometry->GetCenter(); Point3D currentCenter = currentGeometry2D->GetCenter(); /*mitk::ScalarType distance1 = currentGeometry2D->DistanceFromPlane(nodeCenter); mitk::ScalarType distance2 = nodeGeometry->DistanceFromPlane(currentCenter);*/ Vector3D nodeNormal = nodeGeometry->GetNormal(); Vector3D currentNormal = currentGeometry2D->GetNormal(); //Timestep... bool isSameGeometry = ( (nodeCenter == currentCenter) && (nodeNormal == currentNormal)); /*bool isSameGeometry = ( ( nodeGeometry->GetOrigin() == currentGeometry2D->GetOrigin() ) && ( nodeGeometry->GetBounds() == currentGeometry2D->GetBounds() ) && ( nodeGeometry->GetIndexToWorldTransform()->GetMatrix() == currentGeometry2D->GetIndexToWorldTransform()->GetMatrix() ) && ( nodeGeometry->GetIndexToWorldTransform()->GetOffset() == currentGeometry2D->GetIndexToWorldTransform()->GetOffset() ) && ( nodeGeometry->GetSpacing() == currentGeometry2D->GetSpacing() ) && ( nodeGeometry->GetTimeBounds() ) == currentGeometry2D->GetTimeBounds() ) && ( nodeGeometry->GetImageGeometry() == currentGeometry2D->GetImageGeometry() );*/ int stringPosition = nodeName.find( m_Contourmarkername ); if ( ( stringPosition == 0 ) && ( isSameGeometry ) ) return true; } return false; } 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/MitkExt/Interactions/mitkSegTool2D.h b/Modules/MitkExt/Interactions/mitkSegTool2D.h index 5c67b6076f..52e72185c6 100644 --- a/Modules/MitkExt/Interactions/mitkSegTool2D.h +++ b/Modules/MitkExt/Interactions/mitkSegTool2D.h @@ -1,130 +1,134 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #ifndef mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "MitkExtExports.h" #include "mitkTool.h" #include "mitkImage.h" #include "mitkStateEvent.h" #include "mitkPositionEvent.h" //ContourSet - Test #include "mitkContourSet.h" 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 MitkExt_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 ); + itkSetMacro(RememberContourPositions, 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 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*); /** \brief Adds a new node called @a contourmarker which holds a mitk::PlanarFigure. By selecting this node the slices will be oriented according to the planarGeometry */ void AddContourmarker ( const PositionEvent* ); void AddContour ( const mitk::Contour::Pointer ); void InteractiveSegmentationBugMessage( const std::string& message ); + + bool m_RememberContourPositions; private: BaseRenderer* m_LastEventSender; unsigned int m_LastEventSlice; //The prefix of the contourmarkernames. Suffix is a consecutive number const std::string m_Contourmarkername; /** \brief Checks whether a marker for the currentGeometry already exists */ bool ContourmarkerAlreadyExists ( const mitk::PlaneGeometry* currentGeometry2D ); }; } // namespace #endif diff --git a/Modules/MitkExt/Interactions/mitkSetRegionTool.cpp b/Modules/MitkExt/Interactions/mitkSetRegionTool.cpp index cc442927ac..30b7920df2 100644 --- a/Modules/MitkExt/Interactions/mitkSetRegionTool.cpp +++ b/Modules/MitkExt/Interactions/mitkSetRegionTool.cpp @@ -1,352 +1,352 @@ /*========================================================================= Program: Medical Imaging & Interaction Toolkit Language: C++ Date: $Date$ Version: $Revision$ Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. See MITKCopyright.txt or http://www.mitk.org/copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notices for more information. =========================================================================*/ #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) { } 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; // 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 = temporarySlice->GetSliceData()->GetPicDescriptor(); 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; 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; int affectedDimension( -1 ); int affectedSlice( -1 ); FeedbackContourTool::DetermineAffectedImageSlice( image, planeGeometry, affectedDimension, affectedSlice ); 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 ); if ( affectedDimension != -1 ) { // 5. Write the modified 2D working data slice back into the image OverwriteSliceImageFilter::Pointer slicewriter = OverwriteSliceImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( true ); slicewriter->SetSliceImage( slice ); slicewriter->SetSliceDimension( affectedDimension ); slicewriter->SetSliceIndex( affectedSlice ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions ) { this->AddContourmarker(positionEvent); } } else if ( affectedDimension == -1 ) { OverwriteDirectedPlaneImageFilter::Pointer slicewriter = OverwriteDirectedPlaneImageFilter::New(); slicewriter->SetInput( image ); slicewriter->SetCreateUndoInformation( false ); slicewriter->SetSliceImage( slice ); slicewriter->SetPlaneGeometry3D( slice->GetGeometry() ); slicewriter->SetTimeStep( positionEvent->GetSender()->GetTimeStep( image ) ); slicewriter->Update(); - if ( m_ToolManager->GetRememberContourPosition() ) + if ( m_RememberContourPositions ) { this->AddContourmarker(positionEvent); } } else { MITK_ERROR << "FeedbackContourTool could not determine which slice of the image you are drawing on." << std::endl; } // 6. Make sure the result is drawn again --> is visible then. assert( positionEvent->GetSender()->GetRenderWindow() ); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); 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; 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; }