Skip to content

Commit

Permalink
ENH: Add support of shallow and deep copy to scalar volume display node
Browse files Browse the repository at this point in the history
 This is a followup to Slicer@f88c110.
  • Loading branch information
jamesobutler authored Mar 20, 2023
1 parent e468829 commit 76aaf3e
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Applications/SlicerApp/Testing/Python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
slicer_add_python_unittest(SCRIPT DICOMReaders.py)
slicer_add_python_unittest(SCRIPT KneeAtlasTest.py)
slicer_add_python_unittest(SCRIPT sceneImport2428.py)
slicer_add_python_unittest(SCRIPT SlicerDisplayNodeSequenceTest.py)
slicer_add_python_unittest(SCRIPT SlicerMRBMultipleSaveRestoreLoopTest.py)
slicer_add_python_unittest(SCRIPT SlicerMRBMultipleSaveRestoreTest.py)
slicer_add_python_unittest(SCRIPT SlicerMRBSaveRestoreCheckPathsTest.py)
Expand Down Expand Up @@ -463,6 +464,7 @@ if(Slicer_USE_QtTesting AND Slicer_USE_PYTHONQT)
set(KIT_PYTHON_SCRIPTS
AtlasTests.py
sceneImport2428.py
SlicerDisplayNodeSequenceTest.py
SlicerMRBMultipleSaveRestoreLoopTest.py
SlicerMRBMultipleSaveRestoreTest.py
SlicerMRBSaveRestoreCheckPathsTest.py
Expand Down
131 changes: 131 additions & 0 deletions Applications/SlicerApp/Testing/Python/SlicerDisplayNodeSequenceTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import slicer
from slicer.ScriptedLoadableModule import *


#
# SlicerDisplayNodeSequenceTest
#
class SlicerDisplayNodeSequenceTest(ScriptedLoadableModule):
"""Uses ScriptedLoadableModule base class, available at:
https://githuindex_1_display_node.com/Slicer/Slicer/blob/main/Base/Python/slicer/ScriptedLoadableModule.py
"""

def __init__(self, parent):
ScriptedLoadableModule.__init__(self, parent)
self.parent.title = "SlicerDisplayNodeSequenceTest"
self.parent.categories = ["Testing.TestCases"]
self.parent.dependencies = []
self.parent.contributors = ["James Butler (PerkinElmer)"]
self.parent.helpText = """
This test has been added to check that a vtkMRMLScalarVolumeDisplayNode is appropriately copied into a sequence node.
"""
self.parent.acknowledgementText = """
This file was originally developed by James Butler, PerkinElmer.
"""


#
# SlicerDisplayNodeSequenceTestWidget
#
class SlicerDisplayNodeSequenceTestWidget(ScriptedLoadableModuleWidget):
"""
"""

def setup(self):
ScriptedLoadableModuleWidget.setup(self)


#
# SlicerDisplayNodeSequenceTestLogic
#
class SlicerDisplayNodeSequenceTestLogic(ScriptedLoadableModuleLogic):
"""
"""


class SlicerDisplayNodeSequenceTestTest(ScriptedLoadableModuleTest):
"""
"""

def setUp(self):
""" Do whatever is needed to reset the state - typically a scene clear will be enough.
"""
slicer.mrmlScene.Clear(0)

def runTest(self):
"""Run as few or as many tests as needed here.
"""
self.setUp()
self.test_ScalarVolumeDisplayNodeSequence()
self.delayDisplay('Test completed.')

def test_ScalarVolumeDisplayNodeSequence(self):
# Load first volume and apply custom display
import SampleData
sampleDataLogic = SampleData.SampleDataLogic()
mrHead = sampleDataLogic.downloadMRHead()
mrHead.GetDisplayNode().ApplyThresholdOn()
mrHead.GetDisplayNode().SetAutoWindowLevel(False)
mrHead.GetDisplayNode().SetWindowLevelMinMax(10, 120)
min, max = mrHead.GetImageData().GetScalarRange()
mrHead.GetDisplayNode().SetThreshold(0, max)
window_preset = 99
level_preset = 49
mrHead.GetDisplayNode().AddWindowLevelPreset(window_preset, level_preset)

# Load second volume and apply custom display
mrHead2 = sampleDataLogic.downloadMRHead()
mrHead2.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeRed")
mrHead2.GetDisplayNode().ApplyThresholdOn()
min, max = mrHead2.GetImageData().GetScalarRange()
mrHead2.GetDisplayNode().SetThreshold(79, max)
mrHead2.GetDisplayNode().ApplyThresholdOn()
mrHead2.GetDisplayNode().SetAutoWindowLevel(False)
mrHead2.GetDisplayNode().SetWindowLevelMinMax(20, 100)
window_preset = 99
level_preset = 49
mrHead2.GetDisplayNode().AddWindowLevelPreset(window_preset, level_preset)
mrHead2.GetDisplayNode().AddWindowLevelPreset(window_preset - 1, level_preset - 1)

# Create a vtkMRMLScalarVolumeNode sequence
sequence_node = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", "MySequenceNode")
sequence_node.SetDataNodeAtValue(mrHead, "0")
sequence_node.SetDataNodeAtValue(mrHead2, "1")

# Create a vtkMRMLScalarVolumeDisplayNode sequence
sequence_display_node = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", "MySequenceDisplayNode")
sequence_display_node.SetDataNodeAtValue(mrHead.GetDisplayNode(), "0")
sequence_display_node.SetDataNodeAtValue(mrHead2.GetDisplayNode(), "1")

# Synchronize the two sequences and display
browser_node = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceBrowserNode", "MyBrowserNode")
browser_node.AddSynchronizedSequenceNode(sequence_node)
browser_node.AddSynchronizedSequenceNode(sequence_display_node)
volume_proxy_node = browser_node.GetProxyNode(sequence_node)
display_proxy_node = browser_node.GetProxyNode(sequence_display_node)
volume_proxy_node.SetAndObserveDisplayNodeID(display_proxy_node.GetID())
slicer.modules.sequences.toolBar().setActiveBrowserNode(browser_node)
slicer.util.setSliceViewerLayers(background=volume_proxy_node)

# Confirm the display properties have copied over into the Display sequence data nodes
index_0_display_node = sequence_display_node.GetDataNodeAtValue("0")
self.assertTrue(index_0_display_node.GetColorNodeID() == mrHead.GetDisplayNode().GetColorNodeID())
self.assertTrue(index_0_display_node.GetWindowLevelMin() == mrHead.GetDisplayNode().GetWindowLevelMin())
self.assertTrue(index_0_display_node.GetWindowLevelMax() == mrHead.GetDisplayNode().GetWindowLevelMax())
self.assertTrue(index_0_display_node.GetLowerThreshold() == mrHead.GetDisplayNode().GetLowerThreshold())
self.assertTrue(index_0_display_node.GetUpperThreshold() == mrHead.GetDisplayNode().GetUpperThreshold())
self.assertTrue(index_0_display_node.GetNumberOfWindowLevelPresets() == mrHead.GetDisplayNode().GetNumberOfWindowLevelPresets())
for i in range(mrHead.GetDisplayNode().GetNumberOfWindowLevelPresets()):
self.assertTrue(index_0_display_node.GetWindowPreset(i) == mrHead.GetDisplayNode().GetWindowPreset(i))
self.assertTrue(index_0_display_node.GetLevelPreset(i) == mrHead.GetDisplayNode().GetLevelPreset(i))

index_1_display_node = sequence_display_node.GetDataNodeAtValue("1")
self.assertTrue(index_1_display_node.GetColorNodeID() == mrHead2.GetDisplayNode().GetColorNodeID())
self.assertTrue(index_1_display_node.GetWindowLevelMin() == mrHead2.GetDisplayNode().GetWindowLevelMin())
self.assertTrue(index_1_display_node.GetWindowLevelMax() == mrHead2.GetDisplayNode().GetWindowLevelMax())
self.assertTrue(index_1_display_node.GetLowerThreshold() == mrHead2.GetDisplayNode().GetLowerThreshold())
self.assertTrue(index_1_display_node.GetUpperThreshold() == mrHead2.GetDisplayNode().GetUpperThreshold())
self.assertTrue(index_1_display_node.GetNumberOfWindowLevelPresets() == mrHead2.GetDisplayNode().GetNumberOfWindowLevelPresets())
for i in range(mrHead2.GetDisplayNode().GetNumberOfWindowLevelPresets()):
self.assertTrue(index_1_display_node.GetWindowPreset(i) == mrHead2.GetDisplayNode().GetWindowPreset(i))
self.assertTrue(index_1_display_node.GetLevelPreset(i) == mrHead2.GetDisplayNode().GetLevelPreset(i))
17 changes: 8 additions & 9 deletions Libs/MRML/Core/vtkMRMLScalarVolumeDisplayNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,15 @@ void vtkMRMLScalarVolumeDisplayNode::ReadXMLAttributes(const char** atts)
}

//----------------------------------------------------------------------------
// Copy the node\"s attributes to this object.
// Does NOT copy: ID, FilePrefix, Name, VolumeID
void vtkMRMLScalarVolumeDisplayNode::Copy(vtkMRMLNode *anode)
void vtkMRMLScalarVolumeDisplayNode::CopyContent(vtkMRMLNode* anode, bool deepCopy/*=true*/)
{
int disabledModify = this->StartModify();

Superclass::Copy(anode);
vtkMRMLScalarVolumeDisplayNode *node = (vtkMRMLScalarVolumeDisplayNode *) anode;
MRMLNodeModifyBlocker blocker(this);
Superclass::CopyContent(anode, deepCopy);
vtkMRMLScalarVolumeDisplayNode *node = vtkMRMLScalarVolumeDisplayNode::SafeDownCast(anode);
if (!node)
{
return;
}

this->SetAutoWindowLevel( node->GetAutoWindowLevel() );
this->SetWindowLevel(node->GetWindow(), node->GetLevel());
Expand All @@ -372,8 +373,6 @@ void vtkMRMLScalarVolumeDisplayNode::Copy(vtkMRMLNode *anode)
this->AddWindowLevelPreset(node->GetWindowPreset(p), node->GetLevelPreset(p));
}

this->EndModify(disabledModify);

}

//----------------------------------------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions Libs/MRML/Core/vtkMRMLScalarVolumeDisplayNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class VTK_MRML_EXPORT vtkMRMLScalarVolumeDisplayNode : public vtkMRMLVolumeDispl
/// Write this node's information to a MRML file in XML format.
void WriteXML(ostream& of, int indent) override;

///
/// Copy the node's attributes to this object
void Copy(vtkMRMLNode *node) override;
/// Copy node content (excludes basic data, such as name and node references).
/// \sa vtkMRMLNode::CopyContent
vtkMRMLCopyContentMacro(vtkMRMLScalarVolumeDisplayNode);

///
/// Get node XML tag name (like Volume, Model)
Expand Down

0 comments on commit 76aaf3e

Please sign in to comment.