Skip to content

Commit

Permalink
Allow using an input model surface.
Browse files Browse the repository at this point in the history
BranchClipper

 - use a model surface input in addition to a segmentation
 - create segments from an input segmentation
 - create models from an input model
 - do not track branch outputs.
  • Loading branch information
chir-set committed Nov 6, 2024
1 parent a4f318e commit 620035e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 67 deletions.
36 changes: 10 additions & 26 deletions BranchClipper/Resources/UI/qSlicerBranchClipperModuleWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="ctkCollapsibleButton" name="CTKCollapsibleButton">
<widget class="ctkCollapsibleButton" name="inputCollapsibleButton">
<property name="text">
<string>Parameters</string>
<string>Inputs</string>
</property>
<property name="collapsed">
<bool>false</bool>
Expand Down Expand Up @@ -61,29 +61,29 @@
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="segmentationLabel">
<widget class="QLabel" name="surfaceLabel">
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Segmentation:</string>
<string>Tube tree:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QVBoxLayout" name="segmentationLayout">
<layout class="QVBoxLayout" name="surfaceLayout">
<item>
<widget class="qMRMLNodeComboBox" name="segmentationSelector">
<widget class="qMRMLNodeComboBox" name="surfaceSelector">
<property name="toolTip">
<string>Input segmentation.
The input centerline is expected to be inside the lumen surface.</string>
<string>Pick an input segmentation or model node.</string>
</property>
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="nodeTypes">
<stringlist notr="true">
<string>vtkMRMLSegmentationNode</string>
<string>vtkMRMLModelNode</string>
</stringlist>
</property>
<property name="noneEnabled">
Expand All @@ -106,7 +106,7 @@ The input centerline is expected to be inside the lumen surface.</string>
<item>
<widget class="qMRMLSegmentSelectorWidget" name="segmentSelector">
<property name="toolTip">
<string>Select an input segment in the selected segmentation.</string>
<string>Select an input segment.</string>
</property>
<property name="noneEnabled">
<bool>true</bool>
Expand Down Expand Up @@ -249,7 +249,7 @@ The input centerline is expected to be inside the lumen surface.</string>
<connection>
<sender>qSlicerBranchClipperModuleWidget</sender>
<signal>mrmlSceneChanged(vtkMRMLScene*)</signal>
<receiver>segmentationSelector</receiver>
<receiver>surfaceSelector</receiver>
<slot>setMRMLScene(vtkMRMLScene*)</slot>
<hints>
<hint type="sourcelabel">
Expand All @@ -262,22 +262,6 @@ The input centerline is expected to be inside the lumen surface.</string>
</hint>
</hints>
</connection>
<connection>
<sender>segmentationSelector</sender>
<signal>currentNodeChanged(vtkMRMLNode*)</signal>
<receiver>segmentSelector</receiver>
<slot>setCurrentNode(vtkMRMLNode*)</slot>
<hints>
<hint type="sourcelabel">
<x>359</x>
<y>133</y>
</hint>
<hint type="destinationlabel">
<x>359</x>
<y>170</y>
</hint>
</hints>
</connection>
<connection>
<sender>qSlicerBranchClipperModuleWidget</sender>
<signal>mrmlSceneChanged(vtkMRMLScene*)</signal>
Expand Down
124 changes: 83 additions & 41 deletions BranchClipper/qSlicerBranchClipperModuleWidget.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ void qSlicerBranchClipperModuleWidget::setup()

QObject::connect(d->applyButton, SIGNAL(clicked()),
this, SLOT(onApply()));
QObject::connect(d->surfaceSelector, SIGNAL(currentNodeChanged(vtkMRMLNode*)),
this, SLOT(onSurfaceChanged(vtkMRMLNode*)));

d->segmentSelector->setVisible(false);
/*
* Seed with a constant for predictable random table and colours.
* Not using vtkMRMLColorTableNode because we may have more than 256 branches
* or bifurcation profiles.
*/
vtkMath::RandomSeed(7);
}

//-----------------------------------------------------------------------------
void qSlicerBranchClipperModuleWidget::onSurfaceChanged(vtkMRMLNode* surface)
{
Q_D(qSlicerBranchClipperModuleWidget);
// Will be set to None if surface is a model.
d->segmentSelector->setCurrentNode(surface);
vtkMRMLSegmentationNode * segmentation = vtkMRMLSegmentationNode::SafeDownCast(surface);
d->segmentSelector->setVisible(segmentation != nullptr);
}

//-----------------------------------------------------------------------------
Expand All @@ -94,11 +114,12 @@ void qSlicerBranchClipperModuleWidget::onApply()
}
centerlines->DeepCopy(centerlineModel->GetPolyData());

vtkMRMLSegmentationNode * segmentation = nullptr;
vtkMRMLNode * segmentationNode = d->segmentationSelector->currentNode();
if (segmentationNode == nullptr)
vtkMRMLSegmentationNode * inputSegmentationNode = nullptr;
vtkMRMLModelNode * inputModelNode = nullptr;
vtkMRMLNode * surfaceNode = d->surfaceSelector->currentNode();
if (surfaceNode == nullptr)
{
this->showStatusMessage(qSlicerBranchClipperModuleWidget::tr("No segmentation selected."), 5000);
this->showStatusMessage(qSlicerBranchClipperModuleWidget::tr("No surface selected."), 5000);
return;
}
if ((d->bifurcationProfilesToolButton->isChecked() == false)
Expand All @@ -107,36 +128,39 @@ void qSlicerBranchClipperModuleWidget::onApply()
this->showStatusMessage(qSlicerBranchClipperModuleWidget::tr("No output selected."), 5000);
return;
}
// Use controlled segment IDs. We can find and delete them precisely.
std::string segmentID;
std::string segmentName;
// Control segment and model names.
std::string inputSegmentName;
std::string inputModelName;
std::string inputSegmentID;
vtkMRMLSubjectHierarchyNode * shNode = mrmlScene()->GetSubjectHierarchyNode();
vtkIdType shBranchesModelFolderId = 0;

if (segmentationNode && segmentationNode->IsA("vtkMRMLSegmentationNode")
if (surfaceNode && surfaceNode->IsA("vtkMRMLSegmentationNode")
&& (d->branchSegmentsToolButton->isChecked() || d->bifurcationProfilesToolButton->isChecked()) )
{
// Create a closed surface representation of the input segment.
segmentation = vtkMRMLSegmentationNode::SafeDownCast(d->segmentationSelector->currentNode());
if (segmentation->GetSegmentation() == nullptr) // Can it happen ?
inputSegmentationNode = vtkMRMLSegmentationNode::SafeDownCast(d->surfaceSelector->currentNode());
if (inputSegmentationNode->GetSegmentation() == nullptr) // Can it happen ?
{
const QString msg = qSlicerBranchClipperModuleWidget::tr("Segmentation is NULL in MRML node, aborting");
cerr << msg.toStdString() << endl;
this->showStatusMessage(msg, 5000);
return;
}
if (segmentation && segmentation->CreateClosedSurfaceRepresentation())
if (inputSegmentationNode && inputSegmentationNode->CreateClosedSurfaceRepresentation())
{
// ID of input whole segment.
segmentID = d->segmentSelector->currentSegmentID().toStdString();
if (segmentID.empty())
inputSegmentID = d->segmentSelector->currentSegmentID().toStdString();
if (inputSegmentID.empty())
{
const QString msg = qSlicerBranchClipperModuleWidget::tr("No segment selected.");
this->showStatusMessage(msg, 5000);
return;
}
// Name of the input whole segment.
segmentName = segmentation->GetSegmentation()->GetSegment(segmentID)->GetName();
inputSegmentName = inputSegmentationNode->GetSegmentation()->GetSegment(inputSegmentID)->GetName();
// Input whole surface.
segmentation->GetClosedSurfaceRepresentation(segmentID, surface);
inputSegmentationNode->GetClosedSurfaceRepresentation(inputSegmentID, surface);
}
else
{
Expand All @@ -146,6 +170,13 @@ void qSlicerBranchClipperModuleWidget::onApply()
return;
}
}
else if (surfaceNode && surfaceNode->IsA("vtkMRMLModelNode")
&& (d->branchSegmentsToolButton->isChecked() || d->bifurcationProfilesToolButton->isChecked()) )
{
inputModelNode = vtkMRMLModelNode::SafeDownCast(d->surfaceSelector->currentNode());
surface->DeepCopy(inputModelNode->GetPolyData());
inputModelName = inputModelNode->GetName();
}
else
{
// Should not happen.
Expand All @@ -157,7 +188,7 @@ void qSlicerBranchClipperModuleWidget::onApply()

vtkNew<vtkTimerLog> timer;

// Debranch now. Execute() can be a long process on heavy segmentations.
// Split now. Execute() can be a long process on heavy segmentations.
timer->StartTimer();
this->showStatusMessage(qSlicerBranchClipperModuleWidget::tr("Splitting, please wait..."));

Expand Down Expand Up @@ -192,6 +223,13 @@ void qSlicerBranchClipperModuleWidget::onApply()
this->showStatusMessage(msg, 5000);
return;
}
if (inputModelNode)
{
// Create a child folder of the input centerline to contain all created models; only for a input model surface.
vtkIdType shMasterCenterlineId = shNode->GetItemByDataNode(centerlineModel);
shBranchesModelFolderId = shNode->CreateFolderItem(shMasterCenterlineId, qSlicerBranchClipperModuleWidget::tr("Branches").toStdString());
shNode->SetItemExpanded(shBranchesModelFolderId, false);
}
for (vtkIdType i = 0; i < numberOfBranches; i++)
{
timer->StartTimer();
Expand All @@ -213,40 +251,47 @@ void qSlicerBranchClipperModuleWidget::onApply()
this->showStatusMessage(msg, 5000);
continue;
}
if (segmentation)
if (inputSegmentationNode)
{
// Control branch segment name, id and colour.
std::string branchName = segmentName + std::string("_Branch_") + std::to_string((int) i);
const std::string branchId = segmentID + std::string("_Branch_") + std::to_string((int) i);
double colour[3] = {0.0};
// Don't use a black segment on creation.
bool segmentRemoved = false;
std::string branchName = inputSegmentName + std::string("_Branch_") + std::to_string((int) i);
// Don't duplicate on repeat apply.
if (segmentation->GetSegmentation()->GetSegment(branchId))
{
// Keep the branch name if it has been changed in UI.
branchName = segmentation->GetSegmentation()->GetSegment(branchId)->GetName();
// Keep the colour of a segment with known id.
segmentation->GetSegmentation()->GetSegment(branchId)->GetColor(colour);
segmentation->GetSegmentation()->RemoveSegment(branchId);
segmentRemoved = true;
}
/*
* Don't use AddSegmentFromClosedSurfaceRepresentation().
* Parameter segmentId is marked vtkNotUsed().
*/
//segmentation->AddSegmentFromClosedSurfaceRepresentation(branchSurface, branchName, segmentRemoved ? colour : nullptr, branchId);
vtkSmartPointer<vtkSegment> segment = vtkSmartPointer<vtkSegment>::New();
if (segmentRemoved)
{
// Crash on nullptr. No crash with nullptr in other functions like AddSegmentFromClosedSurfaceRepresentation().
segment->SetColor(colour);
}
segment->SetName(branchName.c_str());
segment->SetTag("Segmentation.Status", "inprogress");
if (segment->AddRepresentation(vtkSegmentationConverter::GetClosedSurfaceRepresentationName(), branchSurface))
{
segmentation->GetSegmentation()->AddSegment(segment, branchId);
inputSegmentationNode->GetSegmentation()->AddSegment(segment);
}
else
{
cerr << "Could not add a closed surface representation to a branch segment." << endl;
}
}
else if(inputModelNode)
{
// Control branch model name.
std::string branchName = inputModelName + std::string("_Branch_") + std::to_string((int) i);
vtkMRMLNode * branchNode = this->mrmlScene()->AddNewNodeByClass("vtkMRMLModelNode");
if (not branchNode)
{
cerr << "Could not add branch model: " << i << endl;
}
else
{
vtkMRMLModelNode * branchModelNode = vtkMRMLModelNode::SafeDownCast(branchNode);
branchModelNode->CreateDefaultDisplayNodes();
branchModelNode->SetName(branchName.c_str());
double colour[3] = {vtkMath::Random(), vtkMath::Random(), vtkMath::Random()};
branchModelNode->GetDisplayNode()->SetColor(colour);
branchModelNode->SetAndObservePolyData(branchSurface);
// Reparent in subject hierarchy.
vtkIdType shBranchModelId = shNode->GetItemByDataNode(branchModelNode);
shNode->SetItemParent(shBranchModelId, shBranchesModelFolderId);
}
}

Expand All @@ -269,7 +314,6 @@ void qSlicerBranchClipperModuleWidget::onApply()
this->showStatusMessage(msg, 5000);
return;
}
vtkMRMLSubjectHierarchyNode * shNode = mrmlScene()->GetSubjectHierarchyNode();
if (shNode == nullptr) // ?
{
mrmlScene()->EndState(vtkMRMLScene::BatchProcessState);
Expand All @@ -285,8 +329,6 @@ void qSlicerBranchClipperModuleWidget::onApply()
vtkIdType shMasterCenterlineId = shNode->GetItemByDataNode(centerlineModel);
vtkIdType shFolderId = shNode->CreateFolderItem(shMasterCenterlineId, qSlicerBranchClipperModuleWidget::tr("Bifurcation profiles").toStdString());
shNode->SetItemExpanded(shFolderId, false);
// Seed with a constant for predictable random table and colours.
vtkMath::RandomSeed(7);

for (int i = 0; i < profiledPolyDatas->GetNumberOfItems(); i++)
{
Expand Down
1 change: 1 addition & 0 deletions BranchClipper/qSlicerBranchClipperModuleWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Q_SLICER_QTMODULES_BRANCHCLIPPER_EXPORT qSlicerBranchClipperModuleWidget :

public slots:
void onApply();
void onSurfaceChanged(vtkMRMLNode* surface);

protected:
QScopedPointer<qSlicerBranchClipperModuleWidgetPrivate> d_ptr;
Expand Down

0 comments on commit 620035e

Please sign in to comment.