From 06629c9dbaeed019a00e55040f97dfa5cf4c6965 Mon Sep 17 00:00:00 2001 From: Csaba Pinter Date: Mon, 22 Oct 2018 11:15:45 -0400 Subject: [PATCH] BUG: Fix 3D picking in displayable managers Model and segmentation displayable managers use vtkCellPicker to pick a node in a certain RAS position. The current implementation uses the GetDataSet method to get the first picked dataset. However, that dataset might be one that is not managed by the caller DM. This is fixed by going through all the picked props and finding the first managed prop. --- .../vtkMRMLModelDisplayableManager.cxx | 180 +++++++++++------- .../vtkMRMLModelDisplayableManager.h | 9 +- ...kMRMLSegmentationsDisplayableManager3D.cxx | 51 ++++- 3 files changed, 164 insertions(+), 76 deletions(-) diff --git a/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.cxx b/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.cxx index f1f41ca49..637337e58 100644 --- a/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.cxx +++ b/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.cxx @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,12 @@ public: /// Reset all the pick vars void ResetPick(); + /// Find picked node from mesh and set PickedNodeID in Internal + void FindPickedDisplayNodeFromMesh(vtkPointSet* mesh, double pickedPoint[3]); + /// Find picked point index in mesh and picked cell (PickedCellID) and set PickedPointID in Internal + void FindPickedPointOnMeshAndCell(vtkPointSet* mesh, double pickedPoint[3]); + /// Find first picked node from prop3Ds in cell picker and set PickedNodeID in Internal + void FindFirstPickedDisplayNodeFromPickerProp3Ds(); std::map DisplayedActors; std::map DisplayedNodes; @@ -121,8 +128,8 @@ public: int ClippingMethod; bool ClippingOn; - bool ModelHierarchiesPresent; - bool UpdateHierachyRequested; + bool ModelHierarchiesPresent; + bool UpdateHierachyRequested; vtkSmartPointer WorldPointPicker; vtkSmartPointer PropPicker; @@ -137,7 +144,7 @@ public: // Used for caching the node pointer so that we do not have to search in the scene each time. // We do not add an observer therefore we can let the selection node deleted without our knowledge. - vtkWeakPointer SelectionNode; + vtkWeakPointer SelectionNode; }; //--------------------------------------------------------------------------- @@ -200,6 +207,95 @@ void vtkMRMLModelDisplayableManager::vtkInternal::ResetPick() this->PickedPointID = -1; } +//--------------------------------------------------------------------------- +void vtkMRMLModelDisplayableManager::vtkInternal::FindPickedDisplayNodeFromMesh(vtkPointSet* mesh, double pickedPoint[3]) +{ + if (!mesh) + { + return; + } + + std::map::iterator modelIt; + for (modelIt = this->DisplayedNodes.begin(); modelIt != this->DisplayedNodes.end(); modelIt++) + { + if (modelIt->second != 0) + { + if (vtkMRMLModelDisplayNode::SafeDownCast(modelIt->second) && + vtkMRMLModelDisplayNode::SafeDownCast(modelIt->second)->GetOutputMesh() == mesh) + { + this->PickedDisplayNodeID = modelIt->first; + return; // Display node found + } + } + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLModelDisplayableManager::vtkInternal::FindPickedPointOnMeshAndCell(vtkPointSet* mesh, double pickedPoint[3]) +{ + if (!mesh || this->PickedCellID < 0) + { + return; + } + + // Figure out the closest vertex in the picked cell to the picked RAS + // point. Only doing this on model nodes for now. + vtkCell *cell = mesh->GetCell(this->PickedCellID); + if (!cell) + { + return; + } + + int numPoints = cell->GetNumberOfPoints(); + int closestPointId = -1; + double closestDistance = 0.0l; + for (int p = 0; p < numPoints; p++) + { + int pointId = cell->GetPointId(p); + double *pointCoords = mesh->GetPoint(pointId); + if (pointCoords != 0) + { + double distance = sqrt( pow(pointCoords[0]-pickedPoint[0], 2) + + pow(pointCoords[1]-pickedPoint[1], 2) + + pow(pointCoords[2]-pickedPoint[2], 2) ); + if (p == 0 || distance < closestDistance) + { + closestDistance = distance; + closestPointId = pointId; + } + } + } + this->PickedPointID = closestPointId; +} + +//--------------------------------------------------------------------------- +void vtkMRMLModelDisplayableManager::vtkInternal::FindFirstPickedDisplayNodeFromPickerProp3Ds() +{ + if (!this->CellPicker) + { + return; + } + + vtkProp3DCollection* props = this->CellPicker->GetProp3Ds(); + for (int propIndex=0; propIndexGetNumberOfItems(); ++propIndex) + { + vtkProp3D* pickedProp = vtkProp3D::SafeDownCast(props->GetItemAsObject(propIndex)); + if (!pickedProp) + { + continue; + } + std::map::iterator propIt; + for (propIt = this->DisplayedActors.begin(); propIt != this->DisplayedActors.end(); propIt++) + { + if (pickedProp == propIt->second) + { + this->PickedDisplayNodeID = propIt->first; + return; // Display node found + } + } + } +} + //--------------------------------------------------------------------------- // vtkMRMLModelDisplayableManager methods @@ -1854,7 +1950,7 @@ int vtkMRMLModelDisplayableManager::Pick(int x, int y) // get the pointer to the mesh that the cell was in vtkPointSet *mesh = vtkPointSet::SafeDownCast(this->Internal->CellPicker->GetDataSet()); // now find the model this mesh belongs to - this->FindPickedDisplayNodeFromMesh(mesh, pickPoint); + this->Internal->FindPickedDisplayNodeFromMesh(mesh, pickPoint); } else { @@ -1892,75 +1988,29 @@ int vtkMRMLModelDisplayableManager::Pick3D(double ras[3]) if (this->Internal->CellPicker->Pick3DPoint(ras, ren)) { this->SetPickedCellID(this->Internal->CellPicker->GetCellId()); - // Get the pointer to the mesh that the cell was in - vtkPointSet *mesh = vtkPointSet::SafeDownCast(this->Internal->CellPicker->GetDataSet()); - // Find the model this mesh belongs to - this->FindPickedDisplayNodeFromMesh(mesh, ras); + + // Find first picked model from picker + // Note: Getting the mesh using GetDataSet is not a good solution as the dataset is the first + // one that is picked and it may be of different type (volume, segmentation, etc.) + this->Internal->FindFirstPickedDisplayNodeFromPickerProp3Ds(); + // Find picked point in mesh + vtkMRMLModelDisplayNode* displayNode = vtkMRMLModelDisplayNode::SafeDownCast( + this->GetMRMLScene()->GetNodeByID(this->Internal->PickedDisplayNodeID.c_str()) ); + if (displayNode) + { + this->Internal->FindPickedPointOnMeshAndCell(displayNode->GetOutputMesh(), ras); + } + + this->SetPickedRAS(ras); } #else vtkErrorMacro("Pick3D: This function is only accessible in newer VTK version"); + (void)ras; // not used #endif - this->SetPickedRAS(ras); - return 1; } -//--------------------------------------------------------------------------- -void vtkMRMLModelDisplayableManager::FindPickedDisplayNodeFromMesh(vtkPointSet* mesh, double pickedPoint[3]) -{ - if (!mesh) - { - return; - } - - std::map::iterator modelIter; - for ( modelIter = this->Internal->DisplayedNodes.begin(); - modelIter != this->Internal->DisplayedNodes.end(); - modelIter++ ) - { - vtkDebugMacro("FindPickedDisplayNodeFromMesh: Checking model " << modelIter->first.c_str() << "'s mesh"); - if (modelIter->second != 0) - { - if (vtkMRMLModelDisplayNode::SafeDownCast(modelIter->second) && - vtkMRMLModelDisplayNode::SafeDownCast(modelIter->second)->GetOutputMesh() == mesh) - { - vtkDebugMacro("FindPickedDisplayNodeFromMesh: Found matching mesh, pick was on model " << modelIter->first.c_str()); - this->Internal->PickedDisplayNodeID = modelIter->first; - - // Figure out the closest vertex in the picked cell to the picked RAS - // point. Only doing this on model nodes for now. - vtkCell *cell = mesh->GetCell(this->GetPickedCellID()); - if (cell != 0) - { - int numPoints = cell->GetNumberOfPoints(); - int closestPointId = -1; - double closestDistance = 0.0l; - for (int p = 0; p < numPoints; p++) - { - int pointId = cell->GetPointId(p); - double *pointCoords = mesh->GetPoint(pointId); - if (pointCoords != 0) - { - double distance = sqrt( pow(pointCoords[0]-pickedPoint[0], 2) + - pow(pointCoords[1]-pickedPoint[1], 2) + - pow(pointCoords[2]-pickedPoint[2], 2) ); - if (p == 0 || distance < closestDistance) - { - closestDistance = distance; - closestPointId = pointId; - } - } - } - vtkDebugMacro("FindPickedDisplayNodeFromMesh: found closest point id = " << closestPointId << ", distance = " << closestDistance); - this->SetPickedPointID(closestPointId); - } - continue; - } - } - } -} - //--------------------------------------------------------------------------- const char * vtkMRMLModelDisplayableManager::GetPickedNodeID() { diff --git a/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.h b/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.h index 5784d7a38..081f6c725 100644 --- a/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.h +++ b/Libs/MRML/DisplayableManager/vtkMRMLModelDisplayableManager.h @@ -149,8 +149,10 @@ public: static vtkLookupTable* CreateLookupTableCopy(vtkLookupTable* source); /// Helper function for determining what type of scalar is active. - static bool IsCellScalarsActive(vtkMRMLDisplayNode* displayNode, - vtkMRMLModelNode* model = 0); + /// \return True if attribute location in display node is vtkAssignAttribute::CELL_DATA + /// or active cell scalar name in the model node is vtkDataSetAttributes::SCALARS. + /// False otherwise. + static bool IsCellScalarsActive(vtkMRMLDisplayNode* displayNode, vtkMRMLModelNode* model = 0); protected: @@ -217,9 +219,6 @@ protected: vtkMRMLSelectionNode* GetSelectionNode(); - /// Find picked node and cell from mesh and set PickedNodeID and PickedPointID in Internal - void FindPickedDisplayNodeFromMesh(vtkPointSet* mesh, double pickedPoint[3]); - private: vtkMRMLModelDisplayableManager(const vtkMRMLModelDisplayableManager&); // Not implemented diff --git a/Modules/Loadable/Segmentations/MRMLDM/vtkMRMLSegmentationsDisplayableManager3D.cxx b/Modules/Loadable/Segmentations/MRMLDM/vtkMRMLSegmentationsDisplayableManager3D.cxx index d25908879..fc3e6921e 100644 --- a/Modules/Loadable/Segmentations/MRMLDM/vtkMRMLSegmentationsDisplayableManager3D.cxx +++ b/Modules/Loadable/Segmentations/MRMLDM/vtkMRMLSegmentationsDisplayableManager3D.cxx @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -114,7 +115,10 @@ public: bool UseDisplayableNode(vtkMRMLSegmentationNode* node); void ClearDisplayableNodes(); + /// Find picked node from mesh and set PickedNodeID in Internal void FindPickedDisplayNodeFromMesh(vtkPointSet* mesh); + /// Find first picked node from prop3Ds in cell picker and set PickedNodeID in Internal + void FindFirstPickedDisplayNodeFromPickerProp3Ds(); public: /// Picker of segment prop in renderer @@ -651,11 +655,45 @@ void vtkMRMLSegmentationsDisplayableManager3D::vtkInternal::FindPickedDisplayNod { if (pipelineIt->second->ModelWarper->GetOutput() == mesh) { - vtkDebugWithObjectMacro(currentDisplayNode, "FindPickedDisplayNodeFromMesh: Found matching mesh, pick was on segment " - << pipelineIt->first.c_str() << " of segmentation " - << (currentDisplayNode->GetDisplayableNode() ? currentDisplayNode->GetDisplayableNode()->GetName() : "NULL")); this->PickedDisplayNodeID = currentDisplayNode->GetID(); this->PickedSegmentID = pipelineIt->first; + return; // Display node and segment found + } + } + } +} + +//--------------------------------------------------------------------------- +void vtkMRMLSegmentationsDisplayableManager3D::vtkInternal::FindFirstPickedDisplayNodeFromPickerProp3Ds() +{ + this->PickedDisplayNodeID = ""; + this->PickedSegmentID = ""; + if (!this->CellPicker) + { + return; + } + + vtkProp3DCollection* props = this->CellPicker->GetProp3Ds(); + for (int propIndex=0; propIndexGetNumberOfItems(); ++propIndex) + { + vtkProp3D* pickedProp = vtkProp3D::SafeDownCast(props->GetItemAsObject(propIndex)); + if (!pickedProp) + { + continue; + } + + PipelinesCacheType::iterator pipelinesIt; + for (pipelinesIt = this->DisplayPipelines.begin(); pipelinesIt!=this->DisplayPipelines.end(); ++pipelinesIt) + { + vtkMRMLSegmentationDisplayNode* currentDisplayNode = pipelinesIt->first; + for (PipelineMapType::iterator pipelineIt=pipelinesIt->second.begin(); pipelineIt!=pipelinesIt->second.end(); ++pipelineIt) + { + if (pipelineIt->second->Actor.GetPointer() == pickedProp) + { + this->PickedDisplayNodeID = currentDisplayNode->GetID(); + this->PickedSegmentID = pipelineIt->first; + return; // Display node and segment found + } } } } @@ -854,9 +892,10 @@ int vtkMRMLSegmentationsDisplayableManager3D::Pick3D(double ras[3]) #if VTK_MAJOR_VERSION >= 9 || (VTK_MAJOR_VERSION >= 8 && VTK_MINOR_VERSION >= 2) if (this->Internal->CellPicker->Pick3DPoint(ras, ren)) { - vtkPointSet* mesh = vtkPointSet::SafeDownCast(this->Internal->CellPicker->GetDataSet()); - // Find the segmentation and segment this mesh belongs to - this->Internal->FindPickedDisplayNodeFromMesh(mesh); + // Find first picked segmentation and segment from picker + // Note: Getting the mesh using GetDataSet is not a good solution as the dataset is the first + // one that is picked and it may be of different type (volume, model, etc.) + this->Internal->FindFirstPickedDisplayNodeFromPickerProp3Ds(); } #else vtkErrorMacro("Pick3D: This function is only accessible in newer VTK version"); -- GitLab