Skip to content

Commit

Permalink
ENH: Add real-time mode to Isodose computation
Browse files Browse the repository at this point in the history
Real-time mode can be enabled using the RealTime flag in the Isodose parameter node. When this flag is enabled, the following functions are prevented: use of subject hierarchy to organize the isodose model nodes, reporting of progress, batch processing, and update of dose color table from the isodose one. Instead, top-level isodose model nodes are re-used (by node name) at every computation. It is useful when isodose is needed to be computed on-the-fly for streamed dose data, when one set of isodose surfaces (the "current one") is all that is needed to be kept and displayed.
  • Loading branch information
cpinter committed Nov 29, 2022
1 parent 503f7bc commit 4104533
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 72 deletions.
19 changes: 7 additions & 12 deletions Isodose/Logic/vtkMRMLIsodoseNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
See the License for the specific language governing permissions and
limitations under the License.
This file was originally developed by Kevin Wang, Princess Margaret Cancer Centre
and was supported by Cancer Care Ontario (CCO)'s ACRU program
This file was originally developed by Kevin Wang, Princess Margaret Cancer Centre
and was supported by Cancer Care Ontario (CCO)'s ACRU program
with funds provided by the Ontario Ministry of Health and Long-Term Care
and Ontario Consortium for Adaptive Interventions in Radiation Oncology (OCAIRO).
Expand Down Expand Up @@ -45,15 +45,6 @@ vtkMRMLNodeNewMacro(vtkMRMLIsodoseNode);
//----------------------------------------------------------------------------
vtkMRMLIsodoseNode::vtkMRMLIsodoseNode()
{
this->ShowIsodoseLines = true;
this->ShowIsodoseSurfaces = true;
this->ShowScalarBar = false;
this->ShowScalarBar2D = false;
this->ShowDoseVolumesOnly = true;
this->DoseUnits = DoseUnitsType::Unknown;
this->ReferenceDoseValue = -1.;
this->RelativeRepresentationFlag = false;

this->HideFromEditors = false;
}

Expand All @@ -75,8 +66,9 @@ void vtkMRMLIsodoseNode::WriteXML(ostream& of, int nIndent)
vtkMRMLWriteXMLIntMacro(DoseUnits, DoseUnits);
vtkMRMLWriteXMLFloatMacro(ReferenceDoseValue, ReferenceDoseValue);
vtkMRMLWriteXMLBooleanMacro(RelativeRepresentationFlag, RelativeRepresentationFlag);
vtkMRMLWriteXMLBooleanMacro(RealTime, RealTime);

vtkMRMLWriteXMLEndMacro();
vtkMRMLWriteXMLEndMacro();
}

//----------------------------------------------------------------------------
Expand All @@ -94,6 +86,7 @@ void vtkMRMLIsodoseNode::ReadXMLAttributes(const char** atts)
vtkMRMLReadXMLIntMacro(DoseUnits, DoseUnits);
vtkMRMLReadXMLFloatMacro(ReferenceDoseValue, ReferenceDoseValue);
vtkMRMLReadXMLBooleanMacro(RelativeRepresentationFlag, RelativeRepresentationFlag);
vtkMRMLReadXMLBooleanMacro(RealTime, RealTime);
vtkMRMLReadXMLEndMacro();

this->EndModify(disabledModify);
Expand All @@ -117,6 +110,7 @@ void vtkMRMLIsodoseNode::Copy(vtkMRMLNode *anode)
vtkMRMLCopyIntMacro(DoseUnits);
vtkMRMLCopyFloatMacro(ReferenceDoseValue);
vtkMRMLCopyBooleanMacro(RelativeRepresentationFlag);
vtkMRMLCopyBooleanMacro(RealTime);
vtkMRMLCopyEndMacro();

this->EndModify(disabledModify);
Expand All @@ -136,6 +130,7 @@ void vtkMRMLIsodoseNode::PrintSelf(ostream& os, vtkIndent indent)
vtkMRMLPrintIntMacro(DoseUnits);
vtkMRMLPrintFloatMacro(ReferenceDoseValue);
vtkMRMLPrintBooleanMacro(RelativeRepresentationFlag);
vtkMRMLPrintBooleanMacro(RealTime);
vtkMRMLPrintEndMacro();
}

Expand Down
60 changes: 45 additions & 15 deletions Isodose/Logic/vtkMRMLIsodoseNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
See the License for the specific language governing permissions and
limitations under the License.
This file was originally developed by Kevin Wang, Princess Margaret Cancer Centre
and was supported by Cancer Care Ontario (CCO)'s ACRU program
This file was originally developed by Kevin Wang, Princess Margaret Cancer Centre
and was supported by Cancer Care Ontario (CCO)'s ACRU program
with funds provided by the Ontario Ministry of Health and Long-Term Care
and Ontario Consortium for Adaptive Interventions in Radiation Oncology (OCAIRO).
Expand Down Expand Up @@ -44,19 +44,19 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode
vtkTypeMacro(vtkMRMLIsodoseNode, vtkMRMLNode);
void PrintSelf(ostream& os, vtkIndent indent) override;

/// Create instance of a GAD node.
/// Create instance of a GAD node.
vtkMRMLNode* CreateNodeInstance() override;

/// Set node attributes from name/value pairs
/// Set node attributes from name/value pairs
void ReadXMLAttributes(const char** atts) override;

/// Write this node's information to a MRML file in XML format.
/// 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
/// Copy the node's attributes to this object
void Copy(vtkMRMLNode *node) override;

/// Get unique node XML tag name (like Volume, Model)
/// Get unique node XML tag name (like Volume, Model)
const char* GetNodeTagName() override { return "Isodose"; };

public:
Expand All @@ -70,43 +70,66 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode
/// Set and observe color table node (associated to dose volume node)
void SetAndObserveColorTableNode(vtkMRMLColorTableNode* node);

//@{
/// Get/Set show isodose lines checkbox state
vtkGetMacro(ShowIsodoseLines, bool);
vtkSetMacro(ShowIsodoseLines, bool);
vtkBooleanMacro(ShowIsodoseLines, bool);
//@}

//@{
/// Get/Set show isodose surfaces checkbox state
vtkGetMacro(ShowIsodoseSurfaces, bool);
vtkSetMacro(ShowIsodoseSurfaces, bool);
vtkBooleanMacro(ShowIsodoseSurfaces, bool);
//@}

//@{
/// Get/Set show scalar bar (3D) checkbox state
vtkGetMacro(ShowScalarBar, bool);
vtkSetMacro(ShowScalarBar, bool);
vtkBooleanMacro(ShowScalarBar, bool);
//@}

//@{
/// Get/Set show scalar bar 3D checkbox state
vtkGetMacro(ShowScalarBar2D, bool);
vtkSetMacro(ShowScalarBar2D, bool);
vtkBooleanMacro(ShowScalarBar2D, bool);
//@}

//@{
/// Get/Set show dose volumes only checkbox state
vtkGetMacro(ShowDoseVolumesOnly, bool);
vtkSetMacro(ShowDoseVolumesOnly, bool);
vtkBooleanMacro(ShowDoseVolumesOnly, bool);
//@}

//@{
/// Get/Set reference dose value
vtkGetMacro(ReferenceDoseValue, double);
vtkSetMacro(ReferenceDoseValue, double);
//@}

//@{
/// Get/Set dose units type
vtkGetMacro(DoseUnits, DoseUnitsType);
vtkSetMacro(DoseUnits, DoseUnitsType);
//@}

//@{
/// Get/Set relative representation flag
vtkGetMacro(RelativeRepresentationFlag, bool);
vtkSetMacro(RelativeRepresentationFlag, bool);
vtkBooleanMacro(RelativeRepresentationFlag, bool);
//@}

//@{
/// Get/Set real time flag
vtkGetMacro(RealTime, bool);
vtkSetMacro(RealTime, bool);
vtkBooleanMacro(RealTime, bool);
//@}

protected:
vtkMRMLIsodoseNode();
Expand All @@ -118,29 +141,36 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode

protected:
/// State of Show isodose lines checkbox
bool ShowIsodoseLines;
bool ShowIsodoseLines{true};

/// State of Show isodose surfaces checkbox
bool ShowIsodoseSurfaces;
bool ShowIsodoseSurfaces{true};

/// State of Show scalar bar checkbox
bool ShowScalarBar;
bool ShowScalarBar{false};

/// State of Show scalar bar 2D checkbox
bool ShowScalarBar2D;
bool ShowScalarBar2D{false};

/// State of Show dose volumes only checkbox
bool ShowDoseVolumesOnly;
bool ShowDoseVolumesOnly{true};

/// Type of dose units
DoseUnitsType DoseUnits;
DoseUnitsType DoseUnits{DoseUnitsType::Unknown};

/// Reference dose value
double ReferenceDoseValue;
double ReferenceDoseValue{-1.};

/// Whether use relative isolevels representation
/// for absolute dose (Gy) and unknown units or not
bool RelativeRepresentationFlag;
bool RelativeRepresentationFlag{false};

/// Flag supporting real time applications, when there is strictly one set of isodose surfaces.
/// When this flag is enabled, the following functions are prevented: use of subject hierarchy to organize the isodose
/// model nodes, reporting of progress, batch processing, and update of dose color table from the isodose one.
/// Instead, top-level isodose model nodes are re-used (by node name) at every computation. It is useful when isodose is needed
/// to be computed on-the-fly for streamed dose data, when one set of isodose surfaces is all that is needed to be kept and displayed.
bool RealTime{false};
};

#endif
130 changes: 85 additions & 45 deletions Isodose/Logic/vtkSlicerIsodoseModuleLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -669,21 +669,28 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para
return;
}

scene->StartState(vtkMRMLScene::BatchProcessState);

// Get subject hierarchy item for the dose volume
vtkIdType doseShItemID = shNode->GetItemByDataNode(doseVolumeNode);
if (!doseShItemID)
if (!parameterNode->GetRealTime())
{
vtkErrorMacro("CreateIsodoseSurfaces: Failed to get subject hierarchy item for dose volume '" << doseVolumeNode->GetName() << "'");
return;
scene->StartState(vtkMRMLScene::BatchProcessState);
}

// Check existing isodose set and remove if exists
vtkIdType isodoseFolderItemID = this->GetIsodoseFolderItemID(doseVolumeNode);
if (isodoseFolderItemID)
// Get subject hierarchy item for the dose volume
vtkIdType doseShItemID = 0, isodoseFolderItemID = 0;
if (!parameterNode->GetRealTime())
{
shNode->RemoveItem(isodoseFolderItemID, true, true);
doseShItemID = shNode->GetItemByDataNode(doseVolumeNode);
if (!doseShItemID)
{
vtkErrorMacro("CreateIsodoseSurfaces: Failed to get subject hierarchy item for dose volume '" << doseVolumeNode->GetName() << "'");
return;
}

// Check existing isodose set and remove if exists
vtkIdType isodoseFolderItemID = this->GetIsodoseFolderItemID(doseVolumeNode);
if (isodoseFolderItemID)
{
shNode->RemoveItem(isodoseFolderItemID, true, true);
}
}

// Check if that absolute of relative values
Expand All @@ -703,9 +710,12 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para
vtkSlicerIsodoseModuleLogic::ISODOSE_RELATIVE_ROOT_HIERARCHY_NAME_POSTFIX :
vtkSlicerIsodoseModuleLogic::ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX;

// Setup isodose subject hierarchy folder
std::string isodoseFolderName = std::string(doseVolumeNode->GetName()) + isodoseName;
isodoseFolderItemID = shNode->CreateFolderItem(doseShItemID, isodoseFolderName);
if (!parameterNode->GetRealTime())
{
// Setup isodose subject hierarchy folder
std::string isodoseFolderName = std::string(doseVolumeNode->GetName()) + isodoseName;
isodoseFolderItemID = shNode->CreateFolderItem(doseShItemID, isodoseFolderName);
}

// Get color table
vtkMRMLColorTableNode* colorTableNode = parameterNode->GetColorTableNode();
Expand Down Expand Up @@ -775,9 +785,13 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para
vtkSmartPointer<vtkImageData> reslicedDoseVolumeImage = reslice->GetOutput();

// Report progress
++currentProgressStep;
double progress = (double)(currentProgressStep) / (double)progressStepCount;
this->InvokeEvent(vtkSlicerRtCommon::ProgressUpdated, (void*)&progress);
double progress = 0.0;
if (!parameterNode->GetRealTime())
{
++currentProgressStep;
progress = (double)(currentProgressStep) / (double)progressStepCount;
this->InvokeEvent(vtkSlicerRtCommon::ProgressUpdated, (void*)&progress);
}

// reference value for relative representation
double referenceValue = parameterNode->GetReferenceDoseValue();
Expand Down Expand Up @@ -807,7 +821,9 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para
marchingCubes->ComputeNormalsOff();
marchingCubes->Update();

vtkSmartPointer<vtkPolyData> isoPolyData= marchingCubes->GetOutput();
vtkSmartPointer<vtkPolyData> isoPolyData = marchingCubes->GetOutput();
vtkSmartPointer<vtkMRMLModelNode> isodoseModelNode = nullptr;
std::string isodoseModelNodeName = vtkSlicerIsodoseModuleLogic::ISODOSE_MODEL_NODE_NAME_PREFIX + strIsoLevel + doseUnitName;
if (isoPolyData->GetNumberOfPoints() >= 1)
{
vtkNew<vtkTriangleFilter> triangleFilter;
Expand Down Expand Up @@ -846,44 +862,68 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para
transformPolyData->SetTransform(inputIJKToRASTransform);
transformPolyData->Update();

vtkSmartPointer<vtkMRMLModelDisplayNode> displayNode = vtkSmartPointer<vtkMRMLModelDisplayNode>::New();
displayNode = vtkMRMLModelDisplayNode::SafeDownCast(scene->AddNode(displayNode));
displayNode->Visibility2DOn();
displayNode->VisibilityOn();
displayNode->SetColor(val[0], val[1], val[2]);
displayNode->SetOpacity(val[3]);

// Disable backface culling to make the back side of the model visible as well
displayNode->SetBackfaceCulling(0);

vtkNew<vtkMRMLModelNode> isodoseModelNode;
std::string isodoseModelNodeName = vtkSlicerIsodoseModuleLogic::ISODOSE_MODEL_NODE_NAME_PREFIX + strIsoLevel + doseUnitName;
isodoseModelNode->SetName(isodoseModelNodeName.c_str());
isodoseModelNode->SetSelectable(1);
isodoseModelNode->SetAttribute(vtkSlicerRtCommon::DICOMRTIMPORT_ISODOSE_MODEL_IDENTIFIER_ATTRIBUTE_NAME.c_str(), "1"); // The attribute above distinguishes isodoses from regular models
scene->AddNode(isodoseModelNode);
isodoseModelNode->SetAndObserveDisplayNodeID(displayNode->GetID());
if (parameterNode->GetRealTime())
{
// In real-time mode try to get the isodose model node by name to replace its content
isodoseModelNode = vtkMRMLModelNode::SafeDownCast(scene->GetFirstNodeByName(isodoseModelNodeName.c_str()));
}
if (!isodoseModelNode)
{
vtkMRMLModelDisplayNode* displayNode = vtkMRMLModelDisplayNode::SafeDownCast(scene->AddNewNodeByClass("vtkMRMLModelDisplayNode"));
displayNode->Visibility2DOn();
displayNode->VisibilityOn();
displayNode->SetColor(val[0], val[1], val[2]);
displayNode->SetOpacity(val[3]);
// Disable backface culling to make the back side of the model visible as well
displayNode->SetBackfaceCulling(0);

isodoseModelNode = vtkSmartPointer<vtkMRMLModelNode>::New();
isodoseModelNode->SetName(isodoseModelNodeName.c_str());
isodoseModelNode->SetSelectable(1);
isodoseModelNode->SetAttribute(vtkSlicerRtCommon::DICOMRTIMPORT_ISODOSE_MODEL_IDENTIFIER_ATTRIBUTE_NAME.c_str(), "1"); // The attribute above distinguishes isodoses from regular models
scene->AddNode(isodoseModelNode);
isodoseModelNode->SetAndObserveDisplayNodeID(displayNode->GetID());
shNode->RequestOwnerPluginSearch(isodoseModelNode); //TODO: Why is this needed?
}
isodoseModelNode->SetAndObservePolyData(transformPolyData->GetOutput());
shNode->RequestOwnerPluginSearch(isodoseModelNode); //TODO: Why is this needed?

// Put the new node in the isodose folder
vtkIdType isodoseModelItemID = shNode->GetItemByDataNode(isodoseModelNode);
if (isodoseModelItemID) // There is no automatic SH creation in automatic tests
if (!parameterNode->GetRealTime())
{
vtkIdType isodoseModelItemID = shNode->GetItemByDataNode(isodoseModelNode);
if (isodoseModelItemID) // There is no automatic SH creation in automatic tests
{
shNode->SetItemParent(isodoseModelItemID, isodoseFolderItemID);
}
}
}
else if (parameterNode->GetRealTime())
{
// In real-time mode we need to clear the polydata when there is no output
isodoseModelNode = vtkMRMLModelNode::SafeDownCast(scene->GetFirstNodeByName(isodoseModelNodeName.c_str()));
if (isodoseModelNode)
{
shNode->SetItemParent(isodoseModelItemID, isodoseFolderItemID);
vtkNew<vtkPolyData> emptyPolyData;
isodoseModelNode->SetAndObservePolyData(emptyPolyData);
}
}

// Report progress
++currentProgressStep;
progress = (double)(currentProgressStep) / (double)progressStepCount;
this->InvokeEvent(vtkSlicerRtCommon::ProgressUpdated, (void*)&progress);
if (!parameterNode->GetRealTime())
{
++currentProgressStep;
progress = (double)(currentProgressStep) / (double)progressStepCount;
this->InvokeEvent(vtkSlicerRtCommon::ProgressUpdated, (void*)&progress);
}
} // For all isodose levels

// Update dose color table based on isodose
this->UpdateDoseColorTableFromIsodose(parameterNode);
if (!parameterNode->GetRealTime())
{
// Update dose color table based on isodose
this->UpdateDoseColorTableFromIsodose(parameterNode);

scene->EndState(vtkMRMLScene::BatchProcessState);
scene->EndState(vtkMRMLScene::BatchProcessState);
}
}

//---------------------------------------------------------------------------
Expand Down

0 comments on commit 4104533

Please sign in to comment.