From 02945117abfd2dfb016e6f34547e0bbfc972bf36 Mon Sep 17 00:00:00 2001 From: Nicole Aucoin Date: Fri, 19 Jun 2015 11:14:59 -0400 Subject: [PATCH] ENH: add capability to init from existing segmentations Added an attribute on the finding node to pass along the id of a prior segemented label map volume to use when adding a finding. The module workflow is split up across multiple small functions so the passing of the prior volume information isn't as clean as it could be with a redesign. Issue #1 --- Py/qSlicerLongitudinalPETCTModuleWidget.py | 64 ++++++++- ...LLongitudinalPETCTFindingSettingsDialog.ui | 71 +++++++++- ...LongitudinalPETCTFindingSettingsDialog.cxx | 121 ++++++++++++++++-- ...MLLongitudinalPETCTFindingSettingsDialog.h | 8 +- .../qMRMLLongitudinalPETCTFindingWidget.cxx | 2 +- 5 files changed, 246 insertions(+), 20 deletions(-) diff --git a/Py/qSlicerLongitudinalPETCTModuleWidget.py b/Py/qSlicerLongitudinalPETCTModuleWidget.py index 6caa4b4..ad4c706 100644 --- a/Py/qSlicerLongitudinalPETCTModuleWidget.py +++ b/Py/qSlicerLongitudinalPETCTModuleWidget.py @@ -6,6 +6,7 @@ from SlicerLongitudinalPETCTModuleSegmentationHelper import SlicerLongitudinalPETCTModuleSegmentationHelper as SegmentationHelper from Editor import EditorWidget +from LabelStatistics import LabelStatisticsLogic import sys as SYS @@ -919,11 +920,26 @@ def onFindingNodeAdded(self, finding): name = dialog.property("findingName") colorid = dialog.property("findingColorID") + priorLabelMapID = dialog.property("findingPriorSegmentationID") if (len(name) > 0) & (colorid != -1): finding.SetName(name) finding.SetColorID(colorid) + if (len(priorLabelMapID) > 0): + print 'Initializing segmentation from prior' + study = self.getActiveStudy() + segmentationNode = finding.GetSegmentationMappedByStudyNodeID(study.GetID()) + # Can init from a prior label map if no segmentation node has been + # defined yet, or it has but it doesn't already point to the prior + # label map + if (segmentationNode == None or + segmentationNode.GetLabelVolumeID() == None or + (segmentationNode.GetLabelVolumeID() != None and segmentationNode.GetLabelVolumeID() != priorLabelMapID)): + finding.SetAttribute("AssociatedNodeID", priorLabelMapID) + else: + print 'Unable to initialize from prior label map with id',priorLabelMapID,', segmentationNode = ',segmentationNode + self.activeReportNode.AddFindingNodeID(finding.GetID()) self.reportOverviewWidget.selectFindingRow(self.activeReportNode.GetIndexOfFindingNodeID(finding.GetID())) @@ -1321,7 +1337,47 @@ def __prepareVolumesForSegmentation(self,caller,event): if self.__tempLabelVol == None: self.__createTempLabelVolume() + if finding.GetAttribute("AssociatedNodeID"): + # init from the prior segmentation, then unset + priorSegmentationID = finding.GetAttribute("AssociatedNodeID") + print 'prepareVolumesForSegmentation: Finding has an associated prior: ',priorSegmentationID + priorSegmentationNode = slicer.mrmlScene.GetNodeByID(priorSegmentationID) + if priorSegmentationNode: + # threshold the prior to the finding value + findingColor = finding.GetColorID() + print 'Threshold the prior to the finding color',findingColor + threshold = vtk.vtkImageThreshold() + threshold.SetInputData(priorSegmentationNode.GetImageData()) + threshold.SetReplaceOut(0) + threshold.SetReplaceIn(1) + threshold.SetInValue(findingColor) + # set all non zero values to the finding color + scalarRange = [0,0] + priorSegmentationNode.GetImageData().GetScalarRange(scalarRange) + print '\tprior scalarRange = ',scalarRange + threshold.ThresholdBetween(1, scalarRange[1]) + threshold.Update() + threshold.ReleaseDataFlagOn() + priorSegmentationNode.SetAndObserveImageData(threshold.GetOutputDataObject(0)) + # put the prior into the PET label volume so that it will be in + # the pipeine for segmentation + if (study.GetPETLabelVolumeNode().GetImageData() != None): + # check that the geometry matches + volumesLogic = slicer.modules.volumes.logic() + geometryCheckString = volumesLogic.CheckForLabelVolumeValidity(study.GetPETLabelVolumeNode(), priorSegmentationNode) + if geometryCheckString != "": + print 'Resampling prior due to geometry mismatch:\n',geometryCheckString + resampledSegmentationNode = volumesLogic.ResampleVolumeToReferenceVolume(priorSegmentationNode, study.GetPETLabelVolumeNode()) + study.GetPETLabelVolumeNode().Copy(resampledSegmentationNode) + else: + # geometry matches + study.GetPETLabelVolumeNode().Copy(priorSegmentationNode) + else: + # just copy to init it + study.GetPETLabelVolumeNode().Copy(priorSegmentationNode) + self.__tempLabelVol.Copy(study.GetPETLabelVolumeNode()) + self.__tempLabelVol.SetName("LongitudinalPETCT_CroppedLabelVolume") self.__tempLabelVol.SetSaveWithScene(False) @@ -1386,6 +1442,12 @@ def onAddSegmentationToFinding(self): SegmentationHelper.removeSegmentationFromVolume(seg.GetLabelVolumeNode(), finding.GetColorID()) pasted = SegmentationHelper.pasteFromCroppedToMainLabelVolume(self.__tempLabelVol, seg.GetLabelVolumeNode(), finding.GetColorID()) + + # if there was a prior, unset it now (if done before this, it can get + # over written or cropped out) + if finding.GetAttribute("AssociatedNodeID"): + finding.RemoveAttribute("AssociatedNodeID") + #set coordinates and extent of roi used for segmentation c = [0.,0.,0.] r = [0.,0.,0.] @@ -1445,7 +1507,7 @@ def calculateSUVsAndStatistics(self, study, finding): if (study == None) | (finding == None): return - stats = LabelStatisticsLogic(study.GetPETVolumeNode(), study.GetPETLabelVolumeNode()) + stats = LabelStatistics.LabelStatisticsLogic(study.GetPETVolumeNode(), study.GetPETLabelVolumeNode()) if finding.GetColorID() in stats.labelStats["Labels"]: idx = stats.labelStats["Labels"].index(finding.GetColorID()) diff --git a/Resources/UI/qMRMLLongitudinalPETCTFindingSettingsDialog.ui b/Resources/UI/qMRMLLongitudinalPETCTFindingSettingsDialog.ui index 3cbc372..9ee88e9 100644 --- a/Resources/UI/qMRMLLongitudinalPETCTFindingSettingsDialog.ui +++ b/Resources/UI/qMRMLLongitudinalPETCTFindingSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 374 - 209 + 317 @@ -294,6 +294,56 @@ Node + + + + Select a segmented volume from the scene, resample and threshold + + + Initialize from Prior Segmentation + + + true + + + QFrame::StyledPanel + + + + QFormLayout::FieldsStayAtSizeHint + + + + + Segmentations: + + + + + + + Select a label map volume from the scene. It will be resampled and thresholded if necessary to match the current image geometry. If there are multiple values present, they will be thresholded to the selected finding color. + + + + vtkMRMLLabelMapVolumeNode + + + + true + + + + + + + + + + Qt::Horizontal + + + @@ -306,10 +356,27 @@ Node qMRMLLabelComboBox - QWidget + qMRMLWidget
qMRMLLabelComboBox.h
1
+ + qMRMLNodeComboBox + QWidget +
qMRMLNodeComboBox.h
+
+ + qMRMLWidget + QWidget +
qMRMLWidget.h
+ 1 +
+ + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
diff --git a/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.cxx b/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.cxx index 35ea953..f3bbce6 100644 --- a/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.cxx +++ b/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.cxx @@ -22,13 +22,15 @@ #include "qMRMLLongitudinalPETCTFindingSettingsDialog.h" #include "ui_qMRMLLongitudinalPETCTFindingSettingsDialog.h" -#include +#include +#include +#include + #include +#include +#include -#include - -#include //----------------------------------------------------------------------------- /// \ingroup Slicer_QtModules_LongitudinalPETCT @@ -51,13 +53,14 @@ class qMRMLLongitudinalPETCTFindingSettingsDialogPrivate : public Ui_qMRMLLongit QString findingName; int findingColorID; + QString findingPriorSegmentationID; }; // -------------------------------------------------------------------------- qMRMLLongitudinalPETCTFindingSettingsDialogPrivate::qMRMLLongitudinalPETCTFindingSettingsDialogPrivate( qMRMLLongitudinalPETCTFindingSettingsDialog& object) : - q_ptr(&object), findingName(""), findingColorID(-1) + q_ptr(&object), findingName(""), findingColorID(-1), findingPriorSegmentationID("") { } @@ -77,6 +80,12 @@ qMRMLLongitudinalPETCTFindingSettingsDialogPrivate::setupUi( this->ComboBoxColor->setNoneEnabled(false); QObject::connect( this->ButtonGroupFindingPresets, SIGNAL(buttonClicked(QAbstractButton*)), q, SLOT(presetButtonClicked(QAbstractButton*)) ); + + QObject::connect(this->PriorSegmentationMRMLNodeComboBox, + SIGNAL(currentNodeChanged(vtkMRMLNode*)), + q, + SLOT(priorSegmentationChanged(vtkMRMLNode*))); + } @@ -112,10 +121,15 @@ void qMRMLLongitudinalPETCTFindingSettingsDialog::accept() QString name = d->LineEditName->text(); int id = d->ComboBoxColor->currentColor(); + QString priorLabelMapID; + if (d->PriorSegmentationMRMLNodeComboBox->currentNode()) + { + priorLabelMapID = d->PriorSegmentationMRMLNodeComboBox->currentNodeID(); + } d->findingName = name; d->findingColorID = id; - + d->findingPriorSegmentationID = priorLabelMapID; double c[4]; @@ -148,29 +162,49 @@ void qMRMLLongitudinalPETCTFindingSettingsDialog::accept() QMessageBox::warning(this, "Finding Settings Warning", "The Finding name is already in use, please select another name."); } + // the prior segmentation is optional else { - emit findingSpecified(name, id); - Superclass::accept(); + emit findingSpecified(name, id, priorLabelMapID); + Superclass::accept(); } } } +//----------------------------------------------------------------------------- void qMRMLLongitudinalPETCTFindingSettingsDialog::updateDialogFromMRML() { Q_D(qMRMLLongitudinalPETCTFindingSettingsDialog); Q_ASSERT(d->ComboBoxColor); Q_ASSERT(d->ButtonPresetTumor); - + Q_ASSERT(d->PriorSegmentationMRMLNodeComboBox); vtkMRMLLongitudinalPETCTReportNode* report = this->reportNode(); - if(report) + if (report) { - d->ComboBoxColor->setMRMLColorNode(const_cast(report->GetColorTableNode())); - d->ButtonPresetTumor->click(); + d->ComboBoxColor->setMRMLColorNode(const_cast(report->GetColorTableNode())); + d->ButtonPresetTumor->click(); + + bool enablePriorFlag = 0; + d->PriorSegmentationMRMLNodeComboBox->setMRMLScene(report->GetScene()); + if (this->reportNode()->GetScene() && + this->reportNode()->GetScene()->GetNumberOfNodesByClass("vtkMRMLLabelMapVolumeNode") > 0) + { + int numLabelMaps = this->reportNode()->GetScene()->GetNumberOfNodesByClass("vtkMRMLLabelMapVolumeNode"); + // right now, there's no way to check that there are label map + // volumes in the scene that aren't associated with this study + // (the finding node and active label map node are set after + // hitting accept, the label map node doesn't have any specific + // attributes), so for now if there are more than 2 label + // volumes in the scene, activate it + if (numLabelMaps > 1) + { + enablePriorFlag = true; + } + } + d->InitializeFromPriorSegmentationCollapsibleButton->setEnabled(enablePriorFlag); } - } @@ -212,6 +246,51 @@ qMRMLLongitudinalPETCTFindingSettingsDialog::presetButtonClicked( } } +//----------------------------------------------------------------------------- +void +qMRMLLongitudinalPETCTFindingSettingsDialog::priorSegmentationChanged( + vtkMRMLNode *node) +{ + Q_D(qMRMLLongitudinalPETCTFindingSettingsDialog); + Q_ASSERT(d->PriorSegmentationMRMLNodeComboBox); + + if (!node || !node->GetID()) + { + return; + } + + const char *activeLabelMapID = NULL; + + // check to be sure that it's not the current segmentation associated with + // this finding + if (this->reportNode() && + this->reportNode()->GetActiveStudyNode() && + this->reportNode()->GetActiveFindingNode()) + { + vtkSmartPointer segmentationNode = NULL; + segmentationNode = this->reportNode()->GetActiveFindingNode()->GetSegmentationMappedByStudyNodeID(this->reportNode()->GetActiveStudyNode()->GetID()); + if (segmentationNode) + { + activeLabelMapID = segmentationNode->GetLabelVolumeNodeID(); + + if (!activeLabelMapID) + { + return; + } + if (strcmp(activeLabelMapID, node->GetID()) == 0) + { + qDebug() << "priorSegmentationChanged: selected node is the same as the active label map id, choose another!"; + return; + } + } + else + { + return; + } + } + + this->setFindingPriorSegmentationID(node->GetID()); +} //----------------------------------------------------------------------------- QString @@ -229,6 +308,14 @@ qMRMLLongitudinalPETCTFindingSettingsDialog::findingColorID() return d->findingColorID; } +//----------------------------------------------------------------------------- +QString +qMRMLLongitudinalPETCTFindingSettingsDialog::findingPriorSegmentationID() +{ + Q_D(qMRMLLongitudinalPETCTFindingSettingsDialog); + return d->findingPriorSegmentationID; +} + //----------------------------------------------------------------------------- void qMRMLLongitudinalPETCTFindingSettingsDialog::setFindingName(const QString& name) @@ -244,3 +331,11 @@ qMRMLLongitudinalPETCTFindingSettingsDialog::setFindingColorID(int id) Q_D(qMRMLLongitudinalPETCTFindingSettingsDialog); d->findingColorID = id; } + +//----------------------------------------------------------------------------- +void +qMRMLLongitudinalPETCTFindingSettingsDialog::setFindingPriorSegmentationID(const QString& id) +{ + Q_D(qMRMLLongitudinalPETCTFindingSettingsDialog); + d->findingPriorSegmentationID = id; +} diff --git a/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.h b/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.h index e06f11d..3b8b12f 100644 --- a/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.h +++ b/Widgets/qMRMLLongitudinalPETCTFindingSettingsDialog.h @@ -47,7 +47,7 @@ class Q_SLICER_LONGITUDINALPETCT_MODULE_WIDGETS_EXPORT qMRMLLongitudinalPETCTFin Q_PROPERTY(QString findingName READ findingName WRITE setFindingName) Q_PROPERTY(int findingColorID READ findingColorID WRITE setFindingColorID) - + Q_PROPERTY(QString findingPriorSegmentationID READ findingPriorSegmentationID WRITE setFindingPriorSegmentationID) public: typedef qMRMLLongitudinalPETCTDialog Superclass; @@ -56,12 +56,14 @@ class Q_SLICER_LONGITUDINALPETCT_MODULE_WIDGETS_EXPORT qMRMLLongitudinalPETCTFin QString findingName(); int findingColorID(); + QString findingPriorSegmentationID(); void setFindingName(const QString& name); void setFindingColorID(int colorID); + void setFindingPriorSegmentationID(const QString& labelMapID); signals: - void findingSpecified(const QString& findingName, int colorID); + void findingSpecified(const QString& findingName, int colorID, const QString& findingPriorSegmentationID); public slots: virtual void accept(); @@ -69,7 +71,7 @@ public slots: protected slots: void presetButtonClicked(QAbstractButton* button); - + void priorSegmentationChanged(vtkMRMLNode *node); protected: QScopedPointer d_ptr; diff --git a/Widgets/qMRMLLongitudinalPETCTFindingWidget.cxx b/Widgets/qMRMLLongitudinalPETCTFindingWidget.cxx index b6e2140..7fecf23 100644 --- a/Widgets/qMRMLLongitudinalPETCTFindingWidget.cxx +++ b/Widgets/qMRMLLongitudinalPETCTFindingWidget.cxx @@ -82,7 +82,7 @@ ::setupUi(qMRMLLongitudinalPETCTFindingWidget* widget) this->MRMLNodeComboBoxFinding->setNodeTypes(QStringList("vtkMRMLLongitudinalPETCTFindingNode")); this->MRMLNodeComboBoxFinding->setShowHidden(false); - this->LabelInfo->setToolTip("
-Select/create a Finding
-Place ROI in the slice views around a lesion
-Open built in Editor module with Edit Segmentation to perform the segmentation of the lesion
-Add segmentation to selected Finding in order to confirm the segmentation
"); + this->LabelInfo->setToolTip("
-Select/create a Finding
-You can select an existing label volume to initialize finding segmentation
-Place ROI in the slice views around a lesion
-Open built-in Editor module with Edit Segmentation to perform the segmentation of the lesion
-Add segmentation to selected Finding in order to confirm the segmentation
"); this->ButtonAddSegmentationToFinding->setVisible(false);