Skip to content

Commit

Permalink
ENH: Make Clone action clone a segment when a segment is selected in …
Browse files Browse the repository at this point in the history
…subject hierarchy

Previously, "Clone" action duplicated the entire segmentation node, even when right-clicked on a segment.
This commit changes the behavior so that if the user right-clicks on a Segment and chooses Clone then only that selected segment will be cloned.
  • Loading branch information
lassoan committed Jun 17, 2021
1 parent 8d4bab4 commit a9842ff
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 13 deletions.
18 changes: 13 additions & 5 deletions Libs/MRML/Core/vtkMRMLSubjectHierarchyNode.cxx
Expand Up @@ -120,8 +120,9 @@ class vtkSubjectHierarchyItem : public vtkObject
/// \param parent Parent item pointer under which this item is inserted
/// \param name Name of the item
/// \param level Level string of the item (\sa vtkMRMLSubjectHierarchyConstants). It will be added as a special attribute
/// \param positionUnderParent Position of the item under the parent item. If set to -1 then it will be appended after the last item under the parent.
/// \return ID of the item in the hierarchy that was assigned automatically when adding
vtkIdType AddToTree(vtkSubjectHierarchyItem* parent, std::string name, std::string level);
vtkIdType AddToTree(vtkSubjectHierarchyItem* parent, std::string name, std::string level, int positionUnderParent = -1);

/// Get name of the item. If has data node associated then return name of data node, \sa Name member otherwise
std::string GetName();
Expand Down Expand Up @@ -317,7 +318,7 @@ vtkIdType vtkSubjectHierarchyItem::AddToTree(vtkSubjectHierarchyItem* parent, vt
}

//---------------------------------------------------------------------------
vtkIdType vtkSubjectHierarchyItem::AddToTree(vtkSubjectHierarchyItem* parent, std::string name, std::string level)
vtkIdType vtkSubjectHierarchyItem::AddToTree(vtkSubjectHierarchyItem* parent, std::string name, std::string level, int positionUnderParent/*=-1*/)
{
this->ID = vtkSubjectHierarchyItem::NextSubjectHierarchyItemID;
vtkSubjectHierarchyItem::NextSubjectHierarchyItemID++;
Expand All @@ -338,7 +339,14 @@ vtkIdType vtkSubjectHierarchyItem::AddToTree(vtkSubjectHierarchyItem* parent, st
{
// Add under parent
vtkSmartPointer<vtkSubjectHierarchyItem> childPointer(this);
this->Parent->Children.push_back(childPointer);
if (positionUnderParent<0 || positionUnderParent >= this->Parent->Children.size())
{
this->Parent->Children.push_back(childPointer);
}
else
{
this->Parent->Children.insert(this->Parent->Children.begin() + positionUnderParent, childPointer);
}

// Add to cache (DataNode is nullptr, so no need to add to node cache)
vtkSubjectHierarchyItem::ItemCache[this->ID] = this;
Expand Down Expand Up @@ -2564,7 +2572,7 @@ vtkIdType vtkMRMLSubjectHierarchyNode::CreateItem(vtkIdType parentItemID, vtkMRM
}

//---------------------------------------------------------------------------
vtkIdType vtkMRMLSubjectHierarchyNode::CreateHierarchyItem(vtkIdType parentItemID, std::string name, std::string level)
vtkIdType vtkMRMLSubjectHierarchyNode::CreateHierarchyItem(vtkIdType parentItemID, std::string name, std::string level, int positionUnderParent/*=-1*/)
{
vtkSubjectHierarchyItem* parentItem = this->Internal->FindItemByID(parentItemID);
if (!parentItem)
Expand All @@ -2580,7 +2588,7 @@ vtkIdType vtkMRMLSubjectHierarchyNode::CreateHierarchyItem(vtkIdType parentItemI
this->Internal->AddItemObservers(item);

// Add item to the tree
vtkIdType itemID = item->AddToTree(parentItem, name, level);
vtkIdType itemID = item->AddToTree(parentItem, name, level, positionUnderParent);

// Request owner plugin search (it may depend on the parent etc.)
this->RequestOwnerPluginSearch(itemID);
Expand Down
4 changes: 3 additions & 1 deletion Libs/MRML/Core/vtkMRMLSubjectHierarchyNode.h
Expand Up @@ -206,8 +206,10 @@ class VTK_MRML_EXPORT vtkMRMLSubjectHierarchyNode : public vtkMRMLNode
/// \param parentItemID Parent item under which the created item is inserted. If top-level then use \sa GetSceneItemID
/// \param name Name of the item. Only used if there is no data node associated
/// \param level Level of the hierarchy item. It will be stored as attribute (\sa vtkMRMLSubjectHierarchyConstants)
/// \param positionUnderParent Position of the item under the parent item. If set to -1 then it will be appended
/// after the last item under the parent.
/// \return ID of the item in the hierarchy that was assigned automatically when adding
vtkIdType CreateHierarchyItem(vtkIdType parentItemID, std::string name, std::string level);
vtkIdType CreateHierarchyItem(vtkIdType parentItemID, std::string name, std::string level, int positionUnderParent = -1);
/// Convenience function to create subject item in the hierarchy under a specified parent.
/// \param parentItemID Parent item under which the created item is inserted. If top-level then use \sa GetSceneItemID
/// \param name Name of the item. Only used if there is no data node associated
Expand Down
Expand Up @@ -712,10 +712,19 @@ void qSlicerSubjectHierarchySegmentationsPlugin::onSegmentAdded(vtkObject* calle

if (segmentShItemID == vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID)
{
// Get position of segment under parent
int positionUnderParent = -1;
vtkSegmentation* segmentation = segmentationNode->GetSegmentation();
if (segmentation)
{
positionUnderParent = segmentation->GetSegmentIndex(segmentId);
}

// Add the segment in subject hierarchy to allow individual handling (e.g. visibility)
vtkIdType segmentShItemID = shNode->CreateHierarchyItem(
segmentationShItemID, (segment->GetName() ? segment->GetName() : ""),
vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyVirtualBranchAttributeName());
vtkMRMLSubjectHierarchyConstants::GetSubjectHierarchyVirtualBranchAttributeName(),
segmentation->GetSegmentIndex(segmentId));
shNode->SetItemAttribute(segmentShItemID, vtkMRMLSegmentationNode::GetSegmentIDAttributeName(), segmentId);
// Set plugin for the new item (automatically selects the segment plugin based on confidence values)
qSlicerSubjectHierarchyPluginHandler::instance()->findAndSetOwnerPluginForSubjectHierarchyItem(segmentShItemID);
Expand Down
Expand Up @@ -67,9 +67,10 @@ class qSlicerSubjectHierarchySegmentsPluginPrivate: public QObject
public:
QIcon SegmentIcon;

QAction* ShowOnlyCurrentSegmentAction;
QAction* ShowAllSegmentsAction;
QAction* JumpSlicesAction;
QAction* ShowOnlyCurrentSegmentAction{nullptr};
QAction* ShowAllSegmentsAction{nullptr};
QAction* JumpSlicesAction{nullptr};
QAction* CloneSegmentAction{nullptr};
};

//-----------------------------------------------------------------------------
Expand All @@ -80,9 +81,6 @@ qSlicerSubjectHierarchySegmentsPluginPrivate::qSlicerSubjectHierarchySegmentsPlu
: q_ptr(&object)
, SegmentIcon(QIcon(":Icons/Segment.png"))
{
this->ShowOnlyCurrentSegmentAction = nullptr;
this->ShowAllSegmentsAction = nullptr;
this->JumpSlicesAction = nullptr;
}

//------------------------------------------------------------------------------
Expand All @@ -101,6 +99,12 @@ void qSlicerSubjectHierarchySegmentsPluginPrivate::init()
// Jump slices action
this->JumpSlicesAction = new QAction("Jump slices",q);
QObject::connect(this->JumpSlicesAction, SIGNAL(triggered()), q, SLOT(jumpSlices()));

// Clone segment action
this->CloneSegmentAction = new QAction("Clone", q);
qSlicerSubjectHierarchyAbstractPlugin::setActionPosition(this->CloneSegmentAction,
qSlicerSubjectHierarchyAbstractPlugin::SectionNode, 0.5); // put it right after "Rename" action
QObject::connect(this->CloneSegmentAction, SIGNAL(triggered()), q, SLOT(cloneSegment()));
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -606,6 +610,33 @@ QColor qSlicerSubjectHierarchySegmentsPlugin::getDisplayColor(vtkIdType itemID,
return QColor::fromRgbF(colorArray[0], colorArray[1], colorArray[2]);
}

//---------------------------------------------------------------------------
QList<QAction*> qSlicerSubjectHierarchySegmentsPlugin::itemContextMenuActions()const
{
Q_D(const qSlicerSubjectHierarchySegmentsPlugin);
QList<QAction*> actions;
actions << d->CloneSegmentAction;
return actions;
}

//---------------------------------------------------------------------------
void qSlicerSubjectHierarchySegmentsPlugin::showContextMenuActionsForItem(vtkIdType itemID)
{
Q_D(const qSlicerSubjectHierarchySegmentsPlugin);

qSlicerSubjectHierarchySegmentationsPlugin* segmentationsPlugin = qobject_cast<qSlicerSubjectHierarchySegmentationsPlugin*>(
qSlicerSubjectHierarchyPluginHandler::instance()->pluginByName("Segmentations"));

// Segments plugin shows all segmentations plugin functions in segment context menu
segmentationsPlugin->showContextMenuActionsForItem(itemID);

// Owned Segment
if (this->canOwnSubjectHierarchyItem(itemID) && this->isThisPluginOwnerOfItem(itemID))
{
d->CloneSegmentAction->setVisible(true);
}
}

//---------------------------------------------------------------------------
QList<QAction*> qSlicerSubjectHierarchySegmentsPlugin::visibilityContextMenuActions()const
{
Expand Down Expand Up @@ -844,6 +875,67 @@ void qSlicerSubjectHierarchySegmentsPlugin::jumpSlices()
}
}

//------------------------------------------------------------------------------
void qSlicerSubjectHierarchySegmentsPlugin::cloneSegment()
{
vtkMRMLSubjectHierarchyNode* shNode = qSlicerSubjectHierarchyPluginHandler::instance()->subjectHierarchyNode();
if (!shNode)
{
qCritical() << Q_FUNC_INFO << ": Failed to access subject hierarchy node";
return;
}
vtkIdType currentItemID = qSlicerSubjectHierarchyPluginHandler::instance()->currentItem();
if (currentItemID == vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID)
{
qCritical() << Q_FUNC_INFO << ": Invalid current item";
return;
}

// Get segmentation node
vtkIdType segmentationShItemID = shNode->GetItemParent(currentItemID);
if (segmentationShItemID == vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID)
{
qCritical() << Q_FUNC_INFO << ": Failed to find segmentation subject hierarchy item for segment item " << shNode->GetItemName(currentItemID).c_str();
return;
}
vtkMRMLSegmentationNode* segmentationNode = vtkMRMLSegmentationNode::SafeDownCast(shNode->GetItemDataNode(segmentationShItemID));
if (!segmentationNode || !segmentationNode->GetSegmentation())
{
qCritical() << Q_FUNC_INFO << ": Unable to find segmentation node for segment subject hierarchy item " << shNode->GetItemName(currentItemID).c_str();
return;
}

// Get segment ID
std::string segmentId = shNode->GetItemAttribute(currentItemID, vtkMRMLSegmentationNode::GetSegmentIDAttributeName());

// Get segment and segmentation object
vtkSegmentation* segmentation = segmentationNode->GetSegmentation();
vtkSegment* segment = segmentation->GetSegment(segmentId);
if (!segment)
{
qCritical() << Q_FUNC_INFO << " failed: error getting the segment object";
return;
}

// Copy
vtkNew<vtkSegment> segmentCopy;
segmentCopy->DeepCopy(segment);

// Find the next segment's ID, because we want to insert the copied segment right below the current segment
int segmentIndex = segmentation->GetSegmentIndex(segmentId);
std::string insertBeforeSegmentId;
if (segmentIndex + 1 < segmentation->GetNumberOfSegments())
{
insertBeforeSegmentId = segmentation->GetNthSegmentID(segmentIndex + 1);
}

std::string targetSegmentId = segmentation->GenerateUniqueSegmentID(segmentId);
if (!segmentation->AddSegment(segmentCopy, targetSegmentId, insertBeforeSegmentId))
{
qCritical() << Q_FUNC_INFO << " failed: error adding cloned segment '" << segmentId.c_str() << "' to segmentation";
return;
}
}
//---------------------------------------------------------------------------
bool qSlicerSubjectHierarchySegmentsPlugin::showItemInView(
vtkIdType itemID, vtkMRMLAbstractViewNode* viewNode, vtkIdList* allItemsToShow)
Expand Down
Expand Up @@ -108,6 +108,13 @@ class Q_SLICER_SEGMENTATIONS_PLUGINS_EXPORT qSlicerSubjectHierarchySegmentsPlugi
/// qSlicerTerminologyItemDelegate::ColorAutoGeneratedRole : bool
QColor getDisplayColor(vtkIdType itemID, QMap<int, QVariant> &terminologyMetaData)const override;

/// Get item context menu item actions to add to tree view
QList<QAction*> itemContextMenuActions()const override;

/// Show context menu actions valid for a given subject hierarchy item.
/// \param itemID Subject Hierarchy item to show the context menu items for
void showContextMenuActionsForItem(vtkIdType itemID) override;

/// Get visibility context menu item actions to add to tree view.
/// These item visibility context menu actions can be shown in the implementations of \sa showVisibilityContextMenuActionsForItem
QList<QAction*> visibilityContextMenuActions()const override;
Expand All @@ -132,6 +139,9 @@ protected slots:
/// Jump to slices in slice views to show current segment
void jumpSlices();

/// Clone the selected segment
void cloneSegment();

protected:
QScopedPointer<qSlicerSubjectHierarchySegmentsPluginPrivate> d_ptr;

Expand Down
Expand Up @@ -124,6 +124,13 @@ void qSlicerSubjectHierarchyCloneNodePlugin::showContextMenuActionsForItem(vtkId
return;
}

vtkIdType parentItemID = shNode->GetItemParent(itemID);
if (parentItemID && shNode->IsItemVirtualBranchParent(parentItemID))
{
// This generic plugin does not know how to clone virtual branch items
return;
}

// Show clone node for every non-scene items
d->CloneItemAction->setVisible(true);
}
Expand Down

0 comments on commit a9842ff

Please sign in to comment.