Skip to content

Commit

Permalink
ENH: Add export to color table function to Segment Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
cpinter committed Feb 3, 2025
1 parent 5a4fb1f commit 35c659c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@
#include <vtkITKImageWriter.h>

// MRML includes
#include <vtkEventBroker.h>
#include <vtkMRMLColorTableNode.h>
#include <vtkMRMLLabelMapVolumeNode.h>
#include <vtkMRMLLabelMapVolumeDisplayNode.h>
#include <vtkMRMLMessageCollection.h>
#include <vtkMRMLModelDisplayNode.h>
#include <vtkMRMLModelNode.h>
#include <vtkMRMLScene.h>
#include "vtkMRMLSegmentationNode.h"
#include "vtkMRMLSegmentationDisplayNode.h"
#include "vtkMRMLSegmentationStorageNode.h"
#include "vtkMRMLSegmentEditorNode.h"
#include <vtkMRMLSubjectHierarchyNode.h>
#include <vtkMRMLTransformNode.h>
#include <vtkMRMLColorTableNode.h>
#include <vtkMRMLLabelMapVolumeNode.h>
#include <vtkMRMLLabelMapVolumeDisplayNode.h>
#include <vtkMRMLModelDisplayNode.h>
#include <vtkMRMLModelNode.h>
#include <vtkEventBroker.h>

// STD includes
#include <sstream>
Expand Down Expand Up @@ -1045,6 +1045,106 @@ void vtkSlicerSegmentationsModuleLogic::GenerateMergedLabelmapInReferenceGeometr
}
}

//-----------------------------------------------------------------------------
vtkMRMLColorTableNode* vtkSlicerSegmentationsModuleLogic::CreateColorTableNodeForSegmentation(vtkMRMLSegmentationNode* segmentationNode)
{
if (!segmentationNode)
{
vtkGenericWarningMacro("vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode: Invalid segmentation node");
return nullptr;
}

// Create new color table node if labelmap node doesn't have a color node or if the existing one is not user type
vtkNew<vtkMRMLColorTableNode> newColorTable;
// Need to make the color table node visible because only non-hidden storable nodes are offered to be saved
newColorTable->SetHideFromEditors(false);
std::string colorTableNodeName(segmentationNode->GetName());
colorTableNodeName.append("_ColorTable");
newColorTable->SetName(colorTableNodeName.c_str());
newColorTable->SetTypeToUser();
newColorTable->SetAttribute("Category", "Segmentations");
// Add an item to the color table, otherwise we get a warning when we set it in the display node.
newColorTable->SetNumberOfColors(1);
newColorTable->GetLookupTable()->SetRange(0, 0);
newColorTable->GetLookupTable()->SetNumberOfTableValues(1);
// Use NoName as color name to not list the "background" color in the color legend.
newColorTable->SetColor(0, newColorTable->GetNoName(), 0.0, 0.0, 0.0, 0.0);
segmentationNode->GetScene()->AddNode(newColorTable);
return newColorTable;
}

//-----------------------------------------------------------------------------
bool vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(vtkMRMLSegmentationNode* segmentationNode,
const std::vector<std::string> &segmentIDs, vtkMRMLColorTableNode* colorTableNode)
{
if (!segmentationNode)
{
vtkGenericWarningMacro("vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode: Invalid segmentation node");
return false;
}
if (!colorTableNode)
{
vtkGenericWarningMacro("vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode: Invalid color table node");
return false;
}

std::vector<std::string> exportedSegmentIDs;
if (segmentIDs.empty())
{
segmentationNode->GetSegmentation()->GetSegmentIDs(exportedSegmentIDs);
}
else
{
exportedSegmentIDs = segmentIDs;
}

// Copy segment colors to color table node
vtkMRMLSegmentationDisplayNode* displayNode = vtkMRMLSegmentationDisplayNode::SafeDownCast(segmentationNode->GetDisplayNode());
int numberOfColors = exportedSegmentIDs.size() + 1;

int colorFillStartIndex = 1;
colorTableNode->SetNumberOfColors(numberOfColors);
colorTableNode->GetLookupTable()->SetRange(0, numberOfColors - 1);
colorTableNode->GetLookupTable()->SetNumberOfTableValues(numberOfColors);
// Use NoName as color name to not list the "background" color in the color legend.
colorTableNode->SetColor(0, colorTableNode->GetNoName(), 0.0, 0.0, 0.0, 0.0);

for (int i = colorFillStartIndex; i < colorTableNode->GetNumberOfColors(); ++i)
{
// Fill color table with none
colorTableNode->SetColor(i, colorTableNode->GetNoName(), 0.0, 0.0, 0.0);
}

short colorIndex = 0;
for (std::vector<std::string>::iterator segmentIt = exportedSegmentIDs.begin(); segmentIt != exportedSegmentIDs.end(); ++segmentIt, ++colorIndex)
{
int labelValue = colorIndex + 1;
vtkSegment* segment = segmentationNode->GetSegmentation()->GetSegment(*segmentIt);
if (!segment)
{
vtkWarningWithObjectMacro(segmentationNode,
"ExportSegmentsToColorTableNode: failed to set color table entry, could not find segment by ID " << *segmentIt);
colorTableNode->SetColor(labelValue, colorTableNode->GetNoName(), 0.0, 0.0, 0.0);
continue;
}
const char* segmentName = segment->GetName();
vtkVector3d color = displayNode->GetSegmentColor(*segmentIt);
colorTableNode->SetColor(labelValue, segmentName, color.GetX(), color.GetY(), color.GetZ());
// Set terminology
std::string segmentTerminologyString;
if (segment->GetTag(vtkSegment::GetTerminologyEntryTagName(), segmentTerminologyString))
{
colorTableNode->SetTerminologyFromString(labelValue, segmentTerminologyString);
}
else
{
vtkWarningWithObjectMacro(segmentationNode, "ExportSegmentsToColorTableNode: failed to get terminology tag from segment " << segmentName);
}
}

return true;
}

//-----------------------------------------------------------------------------
bool vtkSlicerSegmentationsModuleLogic::ExportSegmentsToLabelmapNode(vtkMRMLSegmentationNode* segmentationNode,
const std::vector<std::string> &segmentIDs, vtkMRMLLabelMapVolumeNode* labelmapNode, vtkMRMLVolumeNode* referenceVolumeNode /*=nullptr*/,
Expand Down Expand Up @@ -1130,79 +1230,20 @@ bool vtkSlicerSegmentationsModuleLogic::ExportSegmentsToLabelmapNode(vtkMRMLSegm
}
else if (!labelmapNode->GetDisplayNode()->GetColorNode() || labelmapNode->GetDisplayNode()->GetColorNode()->GetType() != vtkMRMLColorNode::User)
{
// Create new color table node if labelmap node doesn't have a color node or if the existing one is not user type
vtkSmartPointer<vtkMRMLColorTableNode> newColorTable = vtkSmartPointer<vtkMRMLColorTableNode>::New();
// Need to make the color table node visible because only non-hidden storable nodes are offered to be saved
newColorTable->SetHideFromEditors(false);
std::string colorTableNodeName(labelmapNode->GetName());
colorTableNodeName.append("_ColorTable");
newColorTable->SetName(colorTableNodeName.c_str());
newColorTable->SetTypeToUser();
newColorTable->SetAttribute("Category", "Segmentations");
// Add an item to the color table, otherwise we get a warning
// when we set it in the display node.
newColorTable->SetNumberOfColors(1);
newColorTable->GetLookupTable()->SetRange(0, 0);
newColorTable->GetLookupTable()->SetNumberOfTableValues(1);
// Use NoName as color name to not list the "background" color in the color legend.
newColorTable->SetColor(0, newColorTable->GetNoName(), 0.0, 0.0, 0.0, 0.0);
labelmapNode->GetScene()->AddNode(newColorTable);
vtkMRMLColorTableNode* newColorTable = vtkSlicerSegmentationsModuleLogic::CreateColorTableNodeForSegmentation(segmentationNode);
if (newColorTable == nullptr)
{
vtkErrorWithObjectMacro(segmentationNode, "ExportSegmentsToLabelmapNode: Failed to create color table node for segmentation "
<< segmentationNode->GetName());
return false;
}
labelmapNode->GetDisplayNode()->SetAndObserveColorNodeID(newColorTable->GetID());
}

// Copy segment colors to color table node
vtkMRMLColorTableNode* colorTableNode = vtkMRMLColorTableNode::SafeDownCast(
labelmapNode->GetDisplayNode()->GetColorNode() ); // Always valid, as it was created just above if was missing
vtkMRMLSegmentationDisplayNode* displayNode = vtkMRMLSegmentationDisplayNode::SafeDownCast(
segmentationNode->GetDisplayNode() );

int numberOfColors = exportedSegmentIDs.size() + 1;
if (labelValues)
{
for (int i = 0; i < static_cast<int>(exportedSegmentIDs.size()); ++i)
{
numberOfColors = std::max(numberOfColors, labelValues->GetValue(i) + 1);
}
}

int colorFillStartIndex = 1;
if (exportColorTable)
{
// If we are using an export color table, we don't want to overwrite the existing values in the table,
// even if they are not used in the segmentation currently.
colorFillStartIndex = exportColorTable->GetNumberOfColors();
}
colorTableNode->SetNumberOfColors(numberOfColors);
colorTableNode->GetLookupTable()->SetRange(0, numberOfColors - 1);
colorTableNode->GetLookupTable()->SetNumberOfTableValues(numberOfColors);
// Use NoName as color name to not list the "background" color in the color legend.
colorTableNode->SetColor(0, colorTableNode->GetNoName(), 0.0, 0.0, 0.0, 0.0);

for (int i = colorFillStartIndex; i < colorTableNode->GetNumberOfColors(); ++i)
{
// Fill color table with none
colorTableNode->SetColor(i, "(none)", 0.0, 0.0, 0.0);
}

short colorIndex = 0;
for (std::vector<std::string>::iterator segmentIt = exportedSegmentIDs.begin(); segmentIt != exportedSegmentIDs.end(); ++segmentIt, ++colorIndex)
{
int labelValue = colorIndex + 1;
if (labelValues)
{
labelValue = labelValues->GetValue(colorIndex);
}
vtkSegment* segment = segmentationNode->GetSegmentation()->GetSegment(*segmentIt);
if (!segment)
{
vtkWarningWithObjectMacro(segmentationNode, "ExportSegmentsToLabelmapNode: failed to set color table entry, could not find segment by ID " << *segmentIt);
colorTableNode->SetColor(labelValue, "(none)", 0.0, 0.0, 0.0);
continue;
}
const char* segmentName = segment->GetName();
vtkVector3d color = displayNode->GetSegmentColor(*segmentIt);
colorTableNode->SetColor(labelValue, segmentName, color.GetX(), color.GetY(), color.GetZ());
}
vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(segmentationNode, segmentIDs, colorTableNode);

// Move exported labelmap node under same parent as segmentation if they are in the same scene
vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(segmentationNode->GetScene());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,20 @@ class VTK_SLICER_SEGMENTATIONS_LOGIC_EXPORT vtkSlicerSegmentationsModuleLogic :
/// \param folderItemId Subject hierarchy folder item ID to export the segments to
static bool ExportAllSegmentsToModels(vtkMRMLSegmentationNode* segmentationNode, vtkIdType folderItemId);

/// Export multiple segments into a multi-label labelmap volume node
/// Create new color table node for segmentation node
/// \param segmentationNode Segmentation node from which the the color table node is created.
/// \return Newly created color table node (that will contain the segment color and terminology information in each color entry).
static vtkMRMLColorTableNode* CreateColorTableNodeForSegmentation(vtkMRMLSegmentationNode* segmentationNode);

/// Export multiple segments into an existing color table node.
/// \param segmentationNode Segmentation node from which the the segments are exported
/// \param segmentIds List of segment IDs to export
/// \param colorTableNode Color table node to export the segment color and terminology information to
/// \return Newly created color table node containing the segment color and terminology information in each color entry.
static bool ExportSegmentsToColorTableNode(
vtkMRMLSegmentationNode* segmentationNode, const std::vector<std::string>& segmentID, vtkMRMLColorTableNode* colorTableNode);

/// Export multiple segments into a multi-label labelmap volume node.
/// \param segmentationNode Segmentation node from which the the segments are exported
/// \param segmentIds List of segment IDs to export
/// \param labelmapNode Labelmap node to export the segments to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ void qMRMLSegmentEditorWidgetPrivate::init()
QAction* exportToFileAction = new QAction(qMRMLSegmentEditorWidget::tr("Export to files..."), segmentationsButtonMenu);
segmentationsButtonMenu->addAction(exportToFileAction);
QObject::connect(exportToFileAction, SIGNAL(triggered()), q, SLOT(onExportToFilesActionClicked()));
QAction* exportToColorNodeAction = new QAction(qMRMLSegmentEditorWidget::tr("Export to color table"), segmentationsButtonMenu);
segmentationsButtonMenu->addAction(exportToColorNodeAction);
QObject::connect(exportToColorNodeAction, SIGNAL(triggered()), q, SLOT(onExportToColorTableActionClicked()));

this->SwitchToSegmentationsButton->setMenu(segmentationsButtonMenu);

Expand Down Expand Up @@ -3522,6 +3525,34 @@ void qMRMLSegmentEditorWidget::onExportToFilesActionClicked()
exportDialog->deleteLater();
}

//-----------------------------------------------------------------------------
void qMRMLSegmentEditorWidget::onExportToColorTableActionClicked()
{
Q_D(qMRMLSegmentEditorWidget);

vtkMRMLSegmentationNode* segmentationNode = d->ParameterSetNode ? d->ParameterSetNode->GetSegmentationNode() : nullptr;
if (!segmentationNode)
{
return;
}

vtkMRMLColorTableNode* newColorTable = vtkSlicerSegmentationsModuleLogic::CreateColorTableNodeForSegmentation(segmentationNode);
if (newColorTable == nullptr)
{
qCritical() << Q_FUNC_INFO << "Failed to create color table node for segmentation " << segmentationNode->GetName();
return;
}

// Export all segments to the new color table
std::vector<std::string> segmentIDs;
if (!vtkSlicerSegmentationsModuleLogic::ExportSegmentsToColorTableNode(segmentationNode, segmentIDs, newColorTable))
{
qCritical() << Q_FUNC_INFO << "Failed to export color and terminology information from segmentation " << segmentationNode->GetName()
<< " to color table " << newColorTable->GetName();
return;
}
}

//-----------------------------------------------------------------------------
void qMRMLSegmentEditorWidget::onSegmentationDisplayModified()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ protected slots:
void onImportExportActionClicked();
/// Open Export to files dialog
void onExportToFilesActionClicked();
/// Export segment color and terminology information to a new color table
void onExportToColorTableActionClicked();

/// Update masking section on the UI
void updateMaskingSection();
Expand Down

0 comments on commit 35c659c

Please sign in to comment.