Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Subject hierarchy speed up with batch processing #1216

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Libs/MRML/Core/vtkMRMLFolderDisplayNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

// MRML includes
#include "vtkMRMLDisplayableNode.h"
#include "vtkMRMLScene.h"
#include "vtkMRMLSubjectHierarchyNode.h"

// VTK includes
Expand Down Expand Up @@ -198,6 +199,13 @@ void vtkMRMLFolderDisplayNode::ChildDisplayNodesModified()
// Get items in branch
std::vector<vtkIdType> childItemIDs;
shNode->GetItemChildren(folderItemId, childItemIDs, true);

bool batchProcessing = (childItemIDs.size() > 10);
jcfr marked this conversation as resolved.
Show resolved Hide resolved
if (batchProcessing)
{
this->GetScene()->StartState(vtkMRMLScene::BatchProcessState);
}

std::vector<vtkIdType>::iterator childIt;
for (childIt=childItemIDs.begin(); childIt!=childItemIDs.end(); ++childIt)
{
Expand All @@ -217,12 +225,17 @@ void vtkMRMLFolderDisplayNode::ChildDisplayNodesModified()
}
} // For all display nodes
}

if (batchProcessing)
{
this->GetScene()->EndState(vtkMRMLScene::BatchProcessState);
}
}

//---------------------------------------------------------------------------
vtkMRMLDisplayNode* vtkMRMLFolderDisplayNode::GetOverridingHierarchyDisplayNode(vtkMRMLDisplayableNode* node)
{
if (!node)
if (!node || !node->GetScene() || node->GetScene()->IsImporting())
{
return nullptr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ void qMRMLSubjectHierarchyModel::setSubjectHierarchyNode(vtkMRMLSubjectHierarchy
this->setColumnCount(oldColumnCount);

// Update whole subject hierarchy
this->updateFromSubjectHierarchy();
this->rebuildFromSubjectHierarchy();

if (shNode)
{
Expand Down Expand Up @@ -677,7 +677,7 @@ bool qMRMLSubjectHierarchyModel::dropMimeData( const QMimeData *data, Qt::DropAc
}

//------------------------------------------------------------------------------
void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy()
void qMRMLSubjectHierarchyModel::rebuildFromSubjectHierarchy()
{
Q_D(qMRMLSubjectHierarchyModel);

Expand Down Expand Up @@ -757,6 +757,50 @@ void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy()
emit subjectHierarchyUpdated();
}

//------------------------------------------------------------------------------
void qMRMLSubjectHierarchyModel::updateFromSubjectHierarchy()
{
Q_D(qMRMLSubjectHierarchyModel);

jcfr marked this conversation as resolved.
Show resolved Hide resolved
if (!d->SubjectHierarchyNode)
{
// Remove all items
const int oldColumnCount = this->columnCount();
this->removeRows(0, this->rowCount());
this->setColumnCount(oldColumnCount);
return;
}
else if (!this->subjectHierarchySceneItem())
{
this->rebuildFromSubjectHierarchy();
return;
}
else
{
// Update the scene item index in case subject hierarchy node has changed
this->subjectHierarchySceneItem()->setData(
QVariant::fromValue(d->SubjectHierarchyNode->GetSceneItemID()), qMRMLSubjectHierarchyModel::SubjectHierarchyItemIDRole );
d->RowCache[d->SubjectHierarchyNode->GetSceneItemID()] = this->subjectHierarchySceneItem()->index();
}


// Get all subject hierarchy items
std::vector<vtkIdType> allItemIDs;
d->SubjectHierarchyNode->GetItemChildren(d->SubjectHierarchyNode->GetSceneItemID(), allItemIDs, true);

// Update expanded states (during inserting the update calls did not find valid indices, so
// expand and collapse statuses were not set in the tree view)
for (std::vector<vtkIdType>::iterator itemIt=allItemIDs.begin(); itemIt!=allItemIDs.end(); ++itemIt)
{
vtkIdType itemID = (*itemIt);
// Expanded states are handled with the name column
QStandardItem* item = this->itemFromSubjectHierarchyItem(itemID, this->nameColumn());
this->updateItemDataFromSubjectHierarchyItem(item, itemID, this->nameColumn());
}

emit subjectHierarchyUpdated();
}

//------------------------------------------------------------------------------
QStandardItem* qMRMLSubjectHierarchyModel::insertSubjectHierarchyItem(vtkIdType itemID)
{
Expand Down Expand Up @@ -897,6 +941,11 @@ void qMRMLSubjectHierarchyModel::updateItemDataFromSubjectHierarchyItem(QStandar
qCritical() << Q_FUNC_INFO << ": Invalid subject hierarchy";
return;
}
if (!item)
{
qCritical() << Q_FUNC_INFO << ": Invalid item";
return;
}
if (shItemID == d->SubjectHierarchyNode->GetSceneItemID())
{
return;
Expand Down Expand Up @@ -1432,7 +1481,7 @@ void qMRMLSubjectHierarchyModel::onSubjectHierarchyItemRemoved(vtkIdType removed
{
return;
}
// The removed item may had children, if they haven't been updated, they are likely to be lost
// The removed item may have had children, if they haven't been updated, they are likely to be lost
// (not reachable when browsing the model), we need to reparent them.
foreach(QList<QStandardItem*> orphans, d->Orphans)
{
Expand Down Expand Up @@ -1466,7 +1515,7 @@ void qMRMLSubjectHierarchyModel::onSubjectHierarchyItemModified(vtkIdType itemID
void qMRMLSubjectHierarchyModel::onMRMLSceneImported(vtkMRMLScene* scene)
{
Q_UNUSED(scene);
this->updateFromSubjectHierarchy();
this->rebuildFromSubjectHierarchy();
}

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -1670,7 +1719,7 @@ void qMRMLSubjectHierarchyModel::updateColumnCount()
this->setColumnCount(max + 1);
if (oldColumnCount == 0)
{
this->updateFromSubjectHierarchy();
this->rebuildFromSubjectHierarchy();
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,13 @@ protected slots:
/// Set the subject hierarchy node found in the given scene. Called only internally.
virtual void setSubjectHierarchyNode(vtkMRMLSubjectHierarchyNode* shNode);

/// Rebuild model from scratch.
/// This is a hard-update that is uses more resources. Use sparingly.
virtual void rebuildFromSubjectHierarchy();
/// Updates properties in the model based on subject hierarchy.
/// This is a soft update that is quick. Calls \sa rebuildFromSubjectHierarchy if necessary.
virtual void updateFromSubjectHierarchy();

virtual QStandardItem* insertSubjectHierarchyItem(vtkIdType itemID);
virtual QStandardItem* insertSubjectHierarchyItem(vtkIdType itemID, QStandardItem* parent, int row=-1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,15 @@ void qMRMLSubjectHierarchyTreeView::toggleSubjectHierarchyItemVisibility(vtkIdTy
return;
}

// If more than 10 item visibilities are changed, then enter in batch processing state
vtkNew<vtkIdList> childItemsList;
d->SubjectHierarchyNode->GetItemChildren(itemID, childItemsList, true);
bool batchProcessing = (childItemsList->GetNumberOfIds() > 10);
if (batchProcessing)
{
d->SubjectHierarchyNode->GetScene()->StartState(vtkMRMLScene::BatchProcessState);
}

qSlicerSubjectHierarchyAbstractPlugin* ownerPlugin =
qSlicerSubjectHierarchyPluginHandler::instance()->getOwnerPluginForSubjectHierarchyItem(itemID);
if (!ownerPlugin)
Expand All @@ -891,6 +900,14 @@ void qMRMLSubjectHierarchyTreeView::toggleSubjectHierarchyItemVisibility(vtkIdTy

int visible = (ownerPlugin->getDisplayVisibility(itemID) > 0 ? 0 : 1);
ownerPlugin->setDisplayVisibility(itemID, visible);

if (batchProcessing)
{
d->SubjectHierarchyNode->GetScene()->EndState(vtkMRMLScene::BatchProcessState);
}

// Trigger view update for the modified item
d->SubjectHierarchyNode->ItemModified(itemID);
}

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -1714,6 +1731,15 @@ void qMRMLSubjectHierarchyTreeView::onMRMLSceneCloseEnded(vtkObject* sceneObject
return;
}

Q_D(qMRMLSubjectHierarchyTreeView);

// Remove selection
QList<vtkIdType> emptySelection;
this->setCurrentItems(emptySelection);
d->SelectedItems.clear();
d->HighlightedItems.clear();


// Get new subject hierarchy node (or if not created yet then trigger creating it, because
// scene close removed the pseudo-singleton subject hierarchy node), and set it to the tree view
this->setSubjectHierarchyNode(vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,10 @@ qSlicerSubjectHierarchyAbstractPlugin* qSlicerSubjectHierarchyPluginHandler::get
std::string ownerPluginName = this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemOwnerPluginName(itemID);
if (ownerPluginName.empty())
{
qCritical() << Q_FUNC_INFO << ": Item '" << this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemName(itemID).c_str() << "' is not owned by any plugin!";
if (itemID != this->m_MRMLScene->GetSubjectHierarchyNode()->GetSceneItemID())
{
qCritical() << Q_FUNC_INFO << ": Item '" << this->m_MRMLScene->GetSubjectHierarchyNode()->GetItemName(itemID).c_str() << "' is not owned by any plugin!";
}
return nullptr;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,16 @@ void qSlicerSubjectHierarchyPluginLogic::setMRMLScene(vtkMRMLScene* scene)
// Connect scene node removed event so if the subject hierarchy node is removed, it is re-created and the hierarchy rebuilt
qvtkReconnect( scene, vtkMRMLScene::NodeRemovedEvent, this, SLOT( onNodeRemoved(vtkObject*,vtkObject*) ) );
// Connect scene import ended event so that subject hierarchy items can be created for supported data nodes if missing (backwards compatibility)
qvtkReconnect( scene, vtkMRMLScene::EndImportEvent, this, SLOT( onSceneImportEnded(vtkObject*) ) );
// Called with high priority so that it is processed here before the model is updated
qvtkReconnect( scene, vtkMRMLScene::EndImportEvent, this, SLOT( onSceneImportEnded(vtkObject*) ), 10.0 );
// Connect scene close ended event so that subject hierarchy can be cleared
qvtkReconnect( scene, vtkMRMLScene::EndCloseEvent, this, SLOT( onSceneCloseEnded(vtkObject*) ) );
// Connect scene restore ended event so that restored subject hierarchy node containing only unresolved items can be resolved
qvtkReconnect( scene, vtkMRMLScene::EndRestoreEvent, this, SLOT( onSceneRestoreEnded(vtkObject*) ) );
// Connect scene batch process ended event so that subject hierarchy is updated after batch processing, when nodes
// may be added/removed without individual events.
// Called with high priority so that it is processed here before the model is updated
qvtkReconnect( scene, vtkMRMLScene::EndBatchProcessEvent, this, SLOT( onSceneBatchProcessEnded(vtkObject*) ), 10.0 );
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -301,6 +306,28 @@ void qSlicerSubjectHierarchyPluginLogic::onSceneImportEnded(vtkObject* sceneObje
this->addSupportedDataNodesToSubjectHierarchy();
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyPluginLogic::onSceneCloseEnded(vtkObject* sceneObject)
{
vtkMRMLScene* scene = vtkMRMLScene::SafeDownCast(sceneObject);
if (!scene)
{
return;
}

// Trigger creating new subject hierarchy node
// (scene close removed the pseudo-singleton subject hierarchy node)
vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene);
if (!shNode)
{
qCritical() << Q_FUNC_INFO << ": There must be a subject hierarchy node in the scene";
return;
}

// Set subject hierarchy node to plugin handler
qSlicerSubjectHierarchyPluginHandler::instance()->observeSubjectHierarchyNode(shNode);
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyPluginLogic::onSceneRestoreEnded(vtkObject* sceneObject)
{
Expand All @@ -316,25 +343,44 @@ void qSlicerSubjectHierarchyPluginLogic::onSceneRestoreEnded(vtkObject* sceneObj
}

//-----------------------------------------------------------------------------
void qSlicerSubjectHierarchyPluginLogic::onSceneCloseEnded(vtkObject* sceneObject)
void qSlicerSubjectHierarchyPluginLogic::onSceneBatchProcessEnded(vtkObject* sceneObject)
{
vtkMRMLScene* scene = vtkMRMLScene::SafeDownCast(sceneObject);
if (!scene)
{
return;
}

// Trigger creating new subject hierarchy node
// (scene close removed the pseudo-singleton subject hierarchy node)
vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::ResolveSubjectHierarchy(scene);
vtkMRMLSubjectHierarchyNode* shNode = scene->GetSubjectHierarchyNode();
if (!shNode)
{
qCritical() << Q_FUNC_INFO << ": There must be a subject hierarchy node in the scene";
return;
}

// Set subject hierarchy node to plugin handler
qSlicerSubjectHierarchyPluginHandler::instance()->observeSubjectHierarchyNode(shNode);
// Go through items, delete the ones that are not folders or virtual items and don't have data nodes
std::vector<vtkIdType> allItemIDs;
shNode->GetItemChildren(shNode->GetSceneItemID(), allItemIDs, true);
for (std::vector<vtkIdType>::iterator itemIt=allItemIDs.begin(); itemIt!=allItemIDs.end(); ++itemIt)
{
vtkIdType itemID = (*itemIt);
if (!shNode->GetItemLevel(itemID).empty())
{
continue; // Folder type item
}
if ( shNode->HasItemAttribute( itemID,
vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyVirtualBranchAttributeName()) )
{
continue; // In virtual branch
}
if (shNode->GetItemDataNode(itemID) == nullptr)
{
shNode->RemoveItem(itemID, false, false);
}
}

// Add data nodes that are supported (i.e. there is a plugin that can claim it) and were not
// in the imported subject hierarchy node to subject hierarchy
this->addSupportedDataNodesToSubjectHierarchy();
}

//-----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ protected slots:
/// Called when scene restore is finished. As the restored node contains only unresolved
/// items, they need to be resolved when restoring ended
void onSceneRestoreEnded(vtkObject* sceneObject);
/// Called when batch processing is ended. Subject hierarchy is updated after batch processing,
/// when nodes may be added/removed without individual events
void onSceneBatchProcessEnded(vtkObject* sceneObject);

protected:
QScopedPointer<qSlicerSubjectHierarchyPluginLogicPrivate> d_ptr;
Expand Down