diff --git a/Isodose/Logic/vtkMRMLIsodoseNode.cxx b/Isodose/Logic/vtkMRMLIsodoseNode.cxx index 3e8844e62..d0365377f 100644 --- a/Isodose/Logic/vtkMRMLIsodoseNode.cxx +++ b/Isodose/Logic/vtkMRMLIsodoseNode.cxx @@ -50,6 +50,9 @@ vtkMRMLIsodoseNode::vtkMRMLIsodoseNode() this->ShowScalarBar = false; this->ShowScalarBar2D = false; this->ShowDoseVolumesOnly = true; + this->DoseUnits = DoseUnitsType::Unknown; + this->ReferenceDoseValue = -1.; + this->RelativeRepresentationFlag = false; this->HideFromEditors = false; } @@ -69,6 +72,10 @@ void vtkMRMLIsodoseNode::WriteXML(ostream& of, int nIndent) vtkMRMLWriteXMLBooleanMacro(ShowScalarBar, ShowScalarBar); vtkMRMLWriteXMLBooleanMacro(ShowScalarBar2D, ShowScalarBar2D); vtkMRMLWriteXMLBooleanMacro(ShowDoseVolumesOnly, ShowDoseVolumesOnly); + vtkMRMLWriteXMLIntMacro(DoseUnits, DoseUnits); + vtkMRMLWriteXMLFloatMacro(ReferenceDoseValue, ReferenceDoseValue); + vtkMRMLWriteXMLBooleanMacro(RelativeRepresentationFlag, RelativeRepresentationFlag); + vtkMRMLWriteXMLEndMacro(); } @@ -84,6 +91,9 @@ void vtkMRMLIsodoseNode::ReadXMLAttributes(const char** atts) vtkMRMLReadXMLBooleanMacro(ShowScalarBar, ShowScalarBar); vtkMRMLReadXMLBooleanMacro(ShowScalarBar2D, ShowScalarBar2D); vtkMRMLReadXMLBooleanMacro(ShowDoseVolumesOnly, ShowDoseVolumesOnly); + vtkMRMLReadXMLIntMacro(DoseUnits, DoseUnits); + vtkMRMLReadXMLFloatMacro(ReferenceDoseValue, ReferenceDoseValue); + vtkMRMLReadXMLBooleanMacro(RelativeRepresentationFlag, RelativeRepresentationFlag); vtkMRMLReadXMLEndMacro(); this->EndModify(disabledModify); @@ -104,6 +114,9 @@ void vtkMRMLIsodoseNode::Copy(vtkMRMLNode *anode) vtkMRMLCopyBooleanMacro(ShowScalarBar); vtkMRMLCopyBooleanMacro(ShowScalarBar2D); vtkMRMLCopyBooleanMacro(ShowDoseVolumesOnly); + vtkMRMLCopyIntMacro(DoseUnits); + vtkMRMLCopyFloatMacro(ReferenceDoseValue); + vtkMRMLCopyBooleanMacro(RelativeRepresentationFlag); vtkMRMLCopyEndMacro(); this->EndModify(disabledModify); @@ -120,6 +133,9 @@ void vtkMRMLIsodoseNode::PrintSelf(ostream& os, vtkIndent indent) vtkMRMLPrintBooleanMacro(ShowScalarBar); vtkMRMLPrintBooleanMacro(ShowScalarBar2D); vtkMRMLPrintBooleanMacro(ShowDoseVolumesOnly); + vtkMRMLPrintIntMacro(DoseUnits); + vtkMRMLPrintFloatMacro(ReferenceDoseValue); + vtkMRMLPrintBooleanMacro(RelativeRepresentationFlag); vtkMRMLPrintEndMacro(); } @@ -172,3 +188,21 @@ void vtkMRMLIsodoseNode::SetAndObserveColorTableNode(vtkMRMLColorTableNode* node doseVolumeNode->SetNodeReferenceID(COLOR_TABLE_REFERENCE_ROLE, (node ? node->GetID() : nullptr)); } + +//---------------------------------------------------------------------------- +void vtkMRMLIsodoseNode::SetDoseUnits(int doseUnits) +{ + switch (doseUnits) + { + case 0: + SetDoseUnits(DoseUnitsType::Gy); + break; + case 1: + SetDoseUnits(DoseUnitsType::Relative); + break; + case -1: + default: + SetDoseUnits(DoseUnitsType::Unknown); + break; + } +} diff --git a/Isodose/Logic/vtkMRMLIsodoseNode.h b/Isodose/Logic/vtkMRMLIsodoseNode.h index 4d3c79096..6bf5d4723 100644 --- a/Isodose/Logic/vtkMRMLIsodoseNode.h +++ b/Isodose/Logic/vtkMRMLIsodoseNode.h @@ -37,6 +37,7 @@ class vtkMRMLColorTableNode; class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode { public: + enum DoseUnitsType { Unknown = -1, Gy = 0, Relative = 1 }; static const char* COLOR_TABLE_REFERENCE_ROLE; static vtkMRMLIsodoseNode *New(); @@ -94,12 +95,27 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode 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); + protected: vtkMRMLIsodoseNode(); ~vtkMRMLIsodoseNode(); vtkMRMLIsodoseNode(const vtkMRMLIsodoseNode&); void operator=(const vtkMRMLIsodoseNode&); + void SetDoseUnits(int doseUnits); + protected: /// State of Show isodose lines checkbox bool ShowIsodoseLines; @@ -115,6 +131,16 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkMRMLIsodoseNode : public vtkMRMLNode /// State of Show dose volumes only checkbox bool ShowDoseVolumesOnly; + + /// Type of dose units + DoseUnitsType DoseUnits; + + /// Reference dose value + double ReferenceDoseValue; + + /// Whether use relative isolevels representation + /// for absolute dose (Gy) and unknown units or not + bool RelativeRepresentationFlag; }; #endif diff --git a/Isodose/Logic/vtkSlicerIsodoseModuleLogic.cxx b/Isodose/Logic/vtkSlicerIsodoseModuleLogic.cxx index 0a44b4670..6852fe047 100644 --- a/Isodose/Logic/vtkSlicerIsodoseModuleLogic.cxx +++ b/Isodose/Logic/vtkSlicerIsodoseModuleLogic.cxx @@ -64,9 +64,11 @@ //---------------------------------------------------------------------------- const char* DEFAULT_ISODOSE_COLOR_TABLE_FILE_NAME = "Isodose_ColorTable.ctbl"; const char* DEFAULT_ISODOSE_COLOR_TABLE_NODE_NAME = "Isodose_ColorTable_Default"; +const char* RELATIVE_ISODOSE_COLOR_TABLE_NODE_NAME = "Isodose_ColorTable_Relative"; const std::string vtkSlicerIsodoseModuleLogic::ISODOSE_MODEL_NODE_NAME_PREFIX = "IsodoseLevel_"; const std::string vtkSlicerIsodoseModuleLogic::ISODOSE_PARAMETER_SET_BASE_NAME_PREFIX = "IsodoseParameterSet_"; const std::string vtkSlicerIsodoseModuleLogic::ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX = "_IsodoseSurfaces"; +const std::string vtkSlicerIsodoseModuleLogic::ISODOSE_RELATIVE_ROOT_HIERARCHY_NAME_POSTFIX = "_RelativeIsodoseSurfaces"; const std::string vtkSlicerIsodoseModuleLogic::ISODOSE_COLOR_TABLE_NODE_NAME_POSTFIX = "_IsodoseColorTable"; //---------------------------------------------------------------------------- @@ -104,6 +106,7 @@ void vtkSlicerIsodoseModuleLogic::SetMRMLSceneInternal(vtkMRMLScene* newScene) if (isodoseColorTableNode) { vtkSlicerIsodoseModuleLogic::CreateDefaultDoseColorTable(newScene); + vtkSlicerIsodoseModuleLogic::CreateRelativeDoseColorTable(newScene); } else { @@ -238,10 +241,8 @@ vtkIdType vtkSlicerIsodoseModuleLogic::GetIsodoseFolderItemID(vtkMRMLNode* node) std::vector doseChildItemIDs; shNode->GetItemChildren(doseShItemID, doseChildItemIDs, false); - std::vector::iterator childIt; - for (childIt=doseChildItemIDs.begin(); childIt!=doseChildItemIDs.end(); ++childIt) + for (vtkIdType childItemID : doseChildItemIDs) { - vtkIdType childItemID = (*childIt); std::string childItemName = shNode->GetItemName(childItemID); std::string childItemNamePostfix = childItemName.substr(childItemName.size() - ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX.size()); if (!childItemNamePostfix.compare(ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX)) @@ -284,14 +285,60 @@ vtkMRMLColorTableNode* vtkSlicerIsodoseModuleLogic::GetDefaultIsodoseColorTable( //colorTableNode->SetAttribute("Category", vtkSlicerRtCommon::SLICERRT_EXTENSION_NAME); colorTableNode->NamesInitialisedOn(); - colorTableNode->SetNumberOfColors(6); - colorTableNode->GetLookupTable()->SetTableRange(0,5); + colorTableNode->SetNumberOfColors(7); + colorTableNode->GetLookupTable()->SetTableRange(0,6); colorTableNode->AddColor("5", 0, 1, 0, 0.2); colorTableNode->AddColor("10", 0.5, 1, 0, 0.2); colorTableNode->AddColor("15", 1, 1, 0, 0.2); colorTableNode->AddColor("20", 1, 0.66, 0, 0.2); colorTableNode->AddColor("25", 1, 0.33, 0, 0.2); colorTableNode->AddColor("30", 1, 0, 0, 0.2); + colorTableNode->AddColor("35", 221./255., 18./255., 123./255., 0.2); + colorTableNode->SaveWithSceneOff(); + + scene->AddNode(colorTableNode); + return colorTableNode; +} + +//------------------------------------------------------------------------------ +vtkMRMLColorTableNode* vtkSlicerIsodoseModuleLogic::GetRelativeIsodoseColorTable(vtkMRMLScene* scene) +{ + if (!scene) + { + vtkGenericWarningMacro("vtkSlicerIsodoseModuleLogic::GetRelativeIsodoseColorTable: Invalid MRML scene"); + return nullptr; + } + + // Check if default color table node already exists + vtkSmartPointer relativeIsodoseColorTableNodes = vtkSmartPointer::Take( + scene->GetNodesByName(RELATIVE_ISODOSE_COLOR_TABLE_NODE_NAME) ); + if (relativeIsodoseColorTableNodes->GetNumberOfItems() > 0) + { + if (relativeIsodoseColorTableNodes->GetNumberOfItems() != 1) + { + vtkWarningWithObjectMacro(scene, "GetRelativeIsodoseColorTable: Multiple default isodose color table nodes found"); + } + + vtkMRMLColorTableNode* isodoseColorTableNode = vtkMRMLColorTableNode::SafeDownCast(relativeIsodoseColorTableNodes->GetItemAsObject(0)); + return isodoseColorTableNode; + } + + // Create default isodose color table if does not yet exist + vtkNew colorTableNode; + colorTableNode->SetName(RELATIVE_ISODOSE_COLOR_TABLE_NODE_NAME); + colorTableNode->SetTypeToUser(); + colorTableNode->SetSingletonTag(RELATIVE_ISODOSE_COLOR_TABLE_NODE_NAME); + //colorTableNode->SetAttribute("Category", vtkSlicerRtCommon::SLICERRT_EXTENSION_NAME); + + colorTableNode->NamesInitialisedOn(); + colorTableNode->SetNumberOfColors(5); + colorTableNode->GetLookupTable()->SetTableRange(0,4); + + colorTableNode->AddColor("80", 108./255., 0., 208./255., 0.2); + colorTableNode->AddColor("90", 0., 147./255., 221./255., 0.2); + colorTableNode->AddColor("100", 1., 1., 0., 0.2); + colorTableNode->AddColor("105", 1., 0.5, 0., 0.2); + colorTableNode->AddColor("110", 1., 0., 0., 0.2); colorTableNode->SaveWithSceneOff(); scene->AddNode(colorTableNode); @@ -390,6 +437,52 @@ vtkMRMLColorTableNode* vtkSlicerIsodoseModuleLogic::CreateDefaultDoseColorTable( return defaultDoseColorTable; } +//------------------------------------------------------------------------------ +vtkMRMLColorTableNode* vtkSlicerIsodoseModuleLogic::CreateRelativeDoseColorTable(vtkMRMLScene* scene) +{ + if (!scene) + { + vtkGenericWarningMacro("vtkSlicerIsodoseModuleLogic::CreateRelativeDoseColorTable: Invalid MRML scene"); + return nullptr; + } + + // Check if default color table node already exists + vtkSmartPointer relativeDoseColorTableNodes = vtkSmartPointer::Take( + scene->GetNodesByName(vtkSlicerRtCommon::RELATIVE_DOSE_COLOR_TABLE_NAME) ); + if (relativeDoseColorTableNodes->GetNumberOfItems() > 0) + { + if (relativeDoseColorTableNodes->GetNumberOfItems() != 1) + { + vtkWarningWithObjectMacro(scene, "CreateRelativeDoseColorTable: Multiple relative dose color table nodes found"); + } + + vtkMRMLColorTableNode* doseColorTable = vtkMRMLColorTableNode::SafeDownCast(relativeDoseColorTableNodes->GetItemAsObject(0)); + return doseColorTable; + } + + // Create relative dose color table if does not yet exist + vtkMRMLColorTableNode* relativeIsodoseColorTable = vtkSlicerIsodoseModuleLogic::GetRelativeIsodoseColorTable(scene); + if (!relativeIsodoseColorTable) + { + vtkErrorWithObjectMacro(scene, "CreateRelativeDoseColorTable: Unable to access relative isodose color table"); + return nullptr; + } + + vtkSmartPointer relativeDoseColorTable = vtkSmartPointer::New(); + relativeDoseColorTable->SetName(vtkSlicerRtCommon::RELATIVE_DOSE_COLOR_TABLE_NAME); + relativeDoseColorTable->SetTypeToFile(); + relativeDoseColorTable->SetSingletonTag(vtkSlicerRtCommon::RELATIVE_DOSE_COLOR_TABLE_NAME); + //relativeDoseColorTable->SetAttribute("Category", vtkSlicerRtCommon::SLICERRT_EXTENSION_NAME); + relativeDoseColorTable->SetNumberOfColors(256); + relativeDoseColorTable->SaveWithSceneOff(); + + // Create dose color table by stretching the isodose color table + vtkSlicerRtCommon::StretchDiscreteColorTable(relativeIsodoseColorTable, relativeDoseColorTable); + + scene->AddNode(relativeDoseColorTable); + return relativeDoseColorTable; +} + //------------------------------------------------------------------------------ vtkMRMLColorTableNode* vtkSlicerIsodoseModuleLogic::SetupColorTableNodeForDoseVolumeNode(vtkMRMLScalarVolumeNode* doseVolumeNode) { @@ -447,36 +540,86 @@ void vtkSlicerIsodoseModuleLogic::SetNumberOfIsodoseLevels(vtkMRMLIsodoseNode* p colorTableNode->SetNumberOfColors(newNumberOfColors); colorTableNode->GetLookupTable()->SetTableRange(0, newNumberOfColors-1); - // Set the default colors in case the number of colors was less than that in the default table - for (int colorIndex=currentNumberOfColors; colorIndexGetDoseUnits(); + + bool relativeFlag = false; + if (parameterNode->GetRelativeRepresentationFlag() + && (doseUnits == vtkMRMLIsodoseNode::Gy + || doseUnits == vtkMRMLIsodoseNode::Unknown)) + { + relativeFlag = true; + } + else if (doseUnits == vtkMRMLIsodoseNode::Relative) { - switch (colorIndex) + relativeFlag = true; + } + + if (!relativeFlag) // absolute values + { + // Set the default colors in case the number of colors was less than that in the default table + for (int colorIndex=currentNumberOfColors; colorIndexSetColor(colorIndex, "5", 0, 1, 0, 0.2); + break; + case 1: + colorTableNode->SetColor(colorIndex, "10", 0.5, 1, 0, 0.2); + break; + case 2: + colorTableNode->SetColor(colorIndex, "15", 1, 1, 0, 0.2); + break; + case 3: + colorTableNode->SetColor(colorIndex, "20", 1, 0.66, 0, 0.2); + break; + case 4: + colorTableNode->SetColor(colorIndex, "25", 1, 0.33, 0, 0.2); + break; + case 5: + colorTableNode->SetColor(colorIndex, "30", 1, 0, 0, 0.2); + break; + case 6: + colorTableNode->SetColor(colorIndex, "35", 221./255., 18./255., 123./255., 0.2); + break; + } + } + // Add colors with index 7 and higher with default gray color + for (int colorIndex=7; colorIndexSetColor(colorIndex, "5", 0, 1, 0, 0.2); - break; - case 1: - colorTableNode->SetColor(colorIndex, "10", 0.5, 1, 0, 0.2); - break; - case 2: - colorTableNode->SetColor(colorIndex, "15", 1, 1, 0, 0.2); - break; - case 3: - colorTableNode->SetColor(colorIndex, "20", 1, 0.66, 0, 0.2); - break; - case 4: - colorTableNode->SetColor(colorIndex, "25", 1, 0.33, 0, 0.2); - break; - case 5: - colorTableNode->SetColor(colorIndex, "30", 1, 0, 0, 0.2); - break; + colorTableNode->SetColor(colorIndex, + vtkSlicerRtCommon::COLOR_VALUE_INVALID[0], vtkSlicerRtCommon::COLOR_VALUE_INVALID[1], vtkSlicerRtCommon::COLOR_VALUE_INVALID[2], 0.2); } } - // Add colors with index 6 and higher with default gray color - for (int colorIndex=6; colorIndexSetColor(colorIndex, - vtkSlicerRtCommon::COLOR_VALUE_INVALID[0], vtkSlicerRtCommon::COLOR_VALUE_INVALID[1], vtkSlicerRtCommon::COLOR_VALUE_INVALID[2], 0.2); + for (int colorIndex=currentNumberOfColors; colorIndexSetColor(colorIndex, "80", 108./255., 0., 208./255., 0.2); + break; + case 1: + colorTableNode->SetColor(colorIndex, "90", 0., 147./255., 221./255., 0.2); + break; + case 2: + colorTableNode->SetColor(colorIndex, "100", 1., 1., 0., 0.2); + break; + case 3: + colorTableNode->SetColor(colorIndex, "105", 1., 0.5, 0., 0.2); + break; + case 4: + colorTableNode->SetColor(colorIndex, "110", 1., 0., 0., 0.2); + break; + } + } + // Add colors with index 5 and higher with default gray color + for (int colorIndex=5; colorIndexSetColor(colorIndex, + vtkSlicerRtCommon::COLOR_VALUE_INVALID[0], vtkSlicerRtCommon::COLOR_VALUE_INVALID[1], vtkSlicerRtCommon::COLOR_VALUE_INVALID[2], 0.2); + } } // Something messes up the category, it needs to be set back to SlicerRT @@ -523,8 +666,25 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para shNode->RemoveItem(isodoseFolderItemID, true, true); } + // Check if that absolute of relative values + bool relativeFlag = false; + vtkMRMLIsodoseNode::DoseUnitsType doseUnits = parameterNode->GetDoseUnits(); + if (parameterNode->GetRelativeRepresentationFlag() + && (doseUnits == vtkMRMLIsodoseNode::Gy + || doseUnits == vtkMRMLIsodoseNode::Unknown)) + { + relativeFlag = true; + } + else if (doseUnits == vtkMRMLIsodoseNode::Relative) + { + relativeFlag = true; + } + std::string isodoseName = relativeFlag ? + 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()) + vtkSlicerIsodoseModuleLogic::ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX; + std::string isodoseFolderName = std::string(doseVolumeNode->GetName()) + isodoseName; isodoseFolderItemID = shNode->CreateFolderItem(doseShItemID, isodoseFolderName); // Get color table @@ -535,6 +695,28 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para return; } + // Check that range is valid for dose and relative dose + // Set dose unit name + std::string doseUnitName = "Gy"; + switch (doseUnits) + { + case vtkMRMLIsodoseNode::Gy: + break; + case vtkMRMLIsodoseNode::Relative: + doseUnitName = "%"; + break; + case vtkMRMLIsodoseNode::Unknown: + default: + doseUnitName = "MU"; + break; + } + + // force percentage dose units for relative isodose representation + if (relativeFlag) + { + doseUnitName = "%"; + } + // Progress int progressStepCount = colorTableNode->GetNumberOfColors() + 1 /* reslice step */; int currentProgressStep = 0; @@ -577,12 +759,23 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para double progress = (double)(currentProgressStep) / (double)progressStepCount; this->InvokeEvent(vtkSlicerRtCommon::ProgressUpdated, (void*)&progress); + // reference value for relative representation + double referenceValue = parameterNode->GetReferenceDoseValue(); + // Create isodose surfaces for (int i = 0; i < colorTableNode->GetNumberOfColors(); i++) { double val[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; const char* strIsoLevel = colorTableNode->GetColorName(i); double isoLevel = vtkVariant(strIsoLevel).ToDouble(); + // change isoLevel value for relative representation + if (relativeFlag) + { + if (doseUnits != vtkMRMLIsodoseNode::Relative) + { + isoLevel = isoLevel * referenceValue / 100.; + } + } colorTableNode->GetColor(i, val); vtkSmartPointer marchingCubes = vtkSmartPointer::New(); @@ -643,10 +836,6 @@ void vtkSlicerIsodoseModuleLogic::CreateIsodoseSurfaces(vtkMRMLIsodoseNode* para // Disable backface culling to make the back side of the model visible as well displayNode->SetBackfaceCulling(0); - // Get dose unit name - std::string doseUnitName = shNode->GetAttributeFromItemAncestor( - doseShItemID, vtkSlicerRtCommon::DICOMRTIMPORT_DOSE_UNIT_NAME_ATTRIBUTE_NAME, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy()); - vtkSmartPointer isodoseModelNode = vtkSmartPointer::New(); std::string isodoseModelNodeName = vtkSlicerIsodoseModuleLogic::ISODOSE_MODEL_NODE_NAME_PREFIX + strIsoLevel + doseUnitName; isodoseModelNode->SetName(isodoseModelNodeName.c_str()); @@ -736,8 +925,8 @@ void vtkSlicerIsodoseModuleLogic::UpdateDoseColorTableFromIsodose(vtkMRMLIsodose int minDoseInDefaultIsodoseLevels = vtkVariant(isodoseColorTableNode->GetColorName(0)).ToInt(); int maxDoseInDefaultIsodoseLevels = vtkVariant(isodoseColorTableNode->GetColorName(isodoseColorTableNode->GetNumberOfColors()-1)).ToInt(); - doseVolumeDisplayNode->AutoWindowLevelOff(); doseVolumeDisplayNode->SetWindowLevelMinMax(minDoseInDefaultIsodoseLevels, maxDoseInDefaultIsodoseLevels); + doseVolumeDisplayNode->AutoWindowLevelOn(); // Get dose grid scaling vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(scene); diff --git a/Isodose/Logic/vtkSlicerIsodoseModuleLogic.h b/Isodose/Logic/vtkSlicerIsodoseModuleLogic.h index 0ed2e34c3..bd9b035af 100644 --- a/Isodose/Logic/vtkSlicerIsodoseModuleLogic.h +++ b/Isodose/Logic/vtkSlicerIsodoseModuleLogic.h @@ -47,6 +47,7 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkSlicerIsodoseModuleLogic : public vtkSl static const std::string ISODOSE_MODEL_NODE_NAME_PREFIX; static const std::string ISODOSE_PARAMETER_SET_BASE_NAME_PREFIX; static const std::string ISODOSE_ROOT_HIERARCHY_NAME_POSTFIX; + static const std::string ISODOSE_RELATIVE_ROOT_HIERARCHY_NAME_POSTFIX; static const std::string ISODOSE_COLOR_TABLE_NODE_NAME_POSTFIX; public: @@ -75,10 +76,16 @@ class VTK_SLICER_ISODOSE_LOGIC_EXPORT vtkSlicerIsodoseModuleLogic : public vtkSl /// Creates default isodose color table. Gets and returns if already exists static vtkMRMLColorTableNode* GetDefaultIsodoseColorTable(vtkMRMLScene* scene); - /// Creates default dose color table (which is the default isodose color table stretched. + /// Creates relative isodose color table. Gets and returns if already exists + static vtkMRMLColorTableNode* GetRelativeIsodoseColorTable(vtkMRMLScene *scene); + + /// Creates default dose color table (which is the default isodose color table stretched). /// Gets and returns if already exists static vtkMRMLColorTableNode* CreateDefaultDoseColorTable(vtkMRMLScene *scene); + /// Creates relative dose color table. Gets and returns if already exists + static vtkMRMLColorTableNode* CreateRelativeDoseColorTable(vtkMRMLScene *scene); + protected: /// Loads default isodose color table from the supplied color table file /// \return The loaded color table node if loading succeeded, nullptr otherwise diff --git a/Isodose/Resources/UI/qSlicerIsodoseModule.ui b/Isodose/Resources/UI/qSlicerIsodoseModule.ui index 214ffee4c..ccd20758d 100644 --- a/Isodose/Resources/UI/qSlicerIsodoseModule.ui +++ b/Isodose/Resources/UI/qSlicerIsodoseModule.ui @@ -6,29 +6,14 @@ 0 0 - 402 - 528 + 373 + 604 Isodose - - - 4 - - - 4 - - - 4 - - - 4 - - - 4 - + @@ -63,41 +48,8 @@ Input - - - 4 - - - 4 - - - 4 - - - 4 - - - 4 - - - - - false - - - - vtkMRMLScalarVolumeNode - - - - false - - - true - - - - + + @@ -237,6 +189,15 @@ + + + + 0 + 0 + 0 + + + @@ -374,6 +335,15 @@ + + + + 0 + 0 + 0 + + + @@ -511,11 +481,21 @@ + + + + 0 + 0 + 0 + + + + Liberation Sans 8 75 true @@ -526,21 +506,25 @@ - - - - true - - - - - - Dose volume: + + + false + + + + vtkMRMLScalarVolumeNode + + + + false + + + true - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -553,14 +537,14 @@ - - + + - Number of iso levels: + Dose volume: - + Show dose volumes only @@ -570,9 +554,132 @@ + + + + true + + + + + + + Number of iso levels: + + + + + + + true + + + Show isolevels as a percentage from the prescribed dose + + + Relative isolevels of dose + + + true + + + true + + + + + + + + true + + + Prescribed or reference dose value + + + Dose value: + + + + + + + true + + + + + + + + + + + Percentage of maximum volume dose: + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 75 + true + + + + Generate isodose + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -655,55 +762,20 @@ - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - - 75 - true - - - - Generate isodose - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + ctkCollapsibleButton + QWidget +
ctkCollapsibleButton.h
+ 1 +
+ + ctkSliderWidget + QWidget +
ctkSliderWidget.h
+
qMRMLColorTableView QTableView @@ -721,12 +793,6 @@
qSlicerWidget.h
1
- - ctkCollapsibleButton - QWidget -
ctkCollapsibleButton.h
- 1 -
@@ -762,5 +828,69 @@ + + groupBox_RelativeIsolevels + toggled(bool) + label_DoseValue + setEnabled(bool) + + + 186 + 173 + + + 65 + 170 + + + + + groupBox_RelativeIsolevels + toggled(bool) + sliderWidget_ReferenceDose + setEnabled(bool) + + + 186 + 173 + + + 223 + 170 + + + + + groupBox_RelativeIsolevels + toggled(bool) + label_PercentageOfMaximumVolumeDose + setEnabled(bool) + + + 186 + 173 + + + 142 + 198 + + + + + groupBox_RelativeIsolevels + toggled(bool) + label_PercentageOfMaxVolumeDose + setEnabled(bool) + + + 186 + 173 + + + 301 + 198 + + + diff --git a/Isodose/qSlicerIsodoseModuleWidget.cxx b/Isodose/qSlicerIsodoseModuleWidget.cxx index 459bef266..1cb0af74d 100644 --- a/Isodose/qSlicerIsodoseModuleWidget.cxx +++ b/Isodose/qSlicerIsodoseModuleWidget.cxx @@ -115,11 +115,9 @@ qSlicerIsodoseModuleWidgetPrivate::qSlicerIsodoseModuleWidgetPrivate(qSlicerIsod this->ScalarBarWidget2DGreen->SetScalarBarActor(this->ScalarBarActor2DGreen); this->ScalarBarWidgets.push_back(this->ScalarBarWidget2DGreen); - for (std::vector::iterator it = this->ScalarBarWidgets.begin(); - it != this->ScalarBarWidgets.end(); ++it) + for (vtkScalarBarWidget* scalarBarWidget : ScalarBarWidgets) { - vtkSlicerRTScalarBarActor* actor = vtkSlicerRTScalarBarActor::SafeDownCast( - (*it)->GetScalarBarActor() ); + vtkSlicerRTScalarBarActor* actor = vtkSlicerRTScalarBarActor::SafeDownCast( scalarBarWidget->GetScalarBarActor() ); actor->SetOrientationToVertical(); actor->SetNumberOfLabels(0); actor->SetMaximumNumberOfColors(0); @@ -206,6 +204,7 @@ void qSlicerIsodoseModuleWidget::setMRMLScene(vtkMRMLScene* scene) this->Superclass::setMRMLScene(scene); qvtkReconnect( d->logic(), scene, vtkMRMLScene::EndImportEvent, this, SLOT(onSceneImportedEvent()) ); + qvtkReconnect( d->logic(), scene, vtkMRMLScene::EndCloseEvent, this, SLOT(onSceneClosedEvent()) ); // Find parameters node or create it if there is no one in the scene if (scene && d->MRMLNodeComboBox_ParameterSet->currentNode() == nullptr) @@ -217,7 +216,7 @@ void qSlicerIsodoseModuleWidget::setMRMLScene(vtkMRMLScene* scene) } else { - vtkSmartPointer newNode = vtkSmartPointer::New(); + vtkNew newNode; this->mrmlScene()->AddNode(newNode); this->setParameterNode(newNode); } @@ -230,6 +229,12 @@ void qSlicerIsodoseModuleWidget::onSceneImportedEvent() this->onEnter(); } +//----------------------------------------------------------------------------- +void qSlicerIsodoseModuleWidget::onSceneClosedEvent() +{ + //TODO: Hide colorbars if they are shown on slices and 3D. +} + //----------------------------------------------------------------------------- void qSlicerIsodoseModuleWidget::enter() { @@ -296,7 +301,10 @@ void qSlicerIsodoseModuleWidget::updateWidgetFromMRML() this->setDoseVolumeNode(d->MRMLNodeComboBox_DoseVolume->currentNode()); } - this->updateScalarBarsFromSelectedColorTable(); + d->groupBox_RelativeIsolevels->setChecked(paramNode->GetRelativeRepresentationFlag()); + + //TODO: It causes a crash when switch from Volumes module to Isodose module +// this->updateScalarBarsFromSelectedColorTable(); d->checkBox_Isoline->setChecked(paramNode->GetShowIsodoseLines()); d->checkBox_Isosurface->setChecked(paramNode->GetShowIsodoseSurfaces()); @@ -336,6 +344,8 @@ void qSlicerIsodoseModuleWidget::setup() connect( d->checkBox_ScalarBar2D, SIGNAL(toggled(bool)), this, SLOT( setScalarBar2DVisibility(bool) ) ); connect( d->pushButton_Apply, SIGNAL(clicked()), this, SLOT(applyClicked()) ); + connect( d->groupBox_RelativeIsolevels, SIGNAL(toggled(bool)), this, SLOT(setRelativeIsolevelsFlag(bool))); + connect( d->sliderWidget_ReferenceDose, SIGNAL(valueChanged(double)), this, SLOT(setReferenceDoseValue(double))); d->pushButton_Apply->setMinimumSize(d->pushButton_Apply->sizeHint().width() + 8, d->pushButton_Apply->sizeHint().height() + 4); @@ -430,6 +440,56 @@ void qSlicerIsodoseModuleWidget::setDoseVolumeNode(vtkMRMLNode* node) if (paramNode->GetDoseVolumeNode() && vtkSlicerRtCommon::IsDoseVolumeNode(paramNode->GetDoseVolumeNode())) { d->label_NotDoseVolumeWarning->setText(""); + + vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(this->mrmlScene()); + if (!shNode) + { + qCritical() << Q_FUNC_INFO << ": Failed to access subject hierarchy node"; + return; + } + + std::string doseUnitName(""); + vtkIdType doseShItemID = shNode->GetItemByDataNode(paramNode->GetDoseVolumeNode()); + if (doseShItemID != vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID) + { + doseUnitName = shNode->GetAttributeFromItemAncestor( + doseShItemID, vtkSlicerRtCommon::DICOMRTIMPORT_DOSE_UNIT_NAME_ATTRIBUTE_NAME, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy()); + } + + double valueRange[2]; + vtkImageData* image = paramNode->GetDoseVolumeNode()->GetImageData(); + image->GetScalarRange(valueRange); + if (!doseUnitName.compare("RELATIVE")) + { + paramNode->SetDoseUnits(vtkMRMLIsodoseNode::Relative); + paramNode->SetReferenceDoseValue(-1.); + d->sliderWidget_ReferenceDose->setEnabled(false); + d->groupBox_RelativeIsolevels->setEnabled(false); + d->sliderWidget_ReferenceDose->setSuffix(""); + } + else if (!doseUnitName.compare("GY")) + { + paramNode->SetDoseUnits(vtkMRMLIsodoseNode::Gy); + paramNode->SetReferenceDoseValue(valueRange[1]); + d->groupBox_RelativeIsolevels->setEnabled(true); + d->sliderWidget_ReferenceDose->setEnabled(true); + d->sliderWidget_ReferenceDose->setMinimum(valueRange[0]); + d->sliderWidget_ReferenceDose->setMaximum(2. * valueRange[1]); + d->sliderWidget_ReferenceDose->setValue(0.87 * valueRange[1]); + d->sliderWidget_ReferenceDose->setSuffix(tr(" Gy")); + } + else + { + paramNode->SetDoseUnits(vtkMRMLIsodoseNode::Unknown); + paramNode->SetReferenceDoseValue(valueRange[1]); + d->groupBox_RelativeIsolevels->setEnabled(true); + d->sliderWidget_ReferenceDose->setEnabled(true); + d->sliderWidget_ReferenceDose->setMinimum(valueRange[0]); + d->sliderWidget_ReferenceDose->setMaximum(2. * valueRange[1]); + d->sliderWidget_ReferenceDose->setValue(0.87 * valueRange[1]); + d->sliderWidget_ReferenceDose->setSuffix(""); + d->label_NotDoseVolumeWarning->setText(tr("Neither \"GY\" nor \"RELATIVE\"")); + } } else { @@ -525,6 +585,121 @@ void qSlicerIsodoseModuleWidget::showDoseVolumesOnlyCheckboxChanged(int aState) } } +//----------------------------------------------------------------------------- +void qSlicerIsodoseModuleWidget::setRelativeIsolevelsFlag(bool useRelativeIsolevels) +{ + Q_D(qSlicerIsodoseModuleWidget); + + if (!this->mrmlScene()) + { + qCritical() << Q_FUNC_INFO << ": Invalid scene"; + return; + } + + vtkMRMLIsodoseNode* paramNode = vtkMRMLIsodoseNode::SafeDownCast(d->MRMLNodeComboBox_ParameterSet->currentNode()); + if (!paramNode) + { + return; + } + + paramNode->DisableModifiedEventOn(); + paramNode->SetRelativeRepresentationFlag(useRelativeIsolevels); + paramNode->DisableModifiedEventOff(); + + vtkMRMLIsodoseNode::DoseUnitsType doseUnits = paramNode->GetDoseUnits(); + + // Get dose unit name and assemble scalar bar title + QString labelHeaderTitle = QObject::tr("Label"); + switch (doseUnits) + { + case vtkMRMLIsodoseNode::Gy: + { + QString msg = useRelativeIsolevels ? QObject::tr("Relative Dose (%)") : QObject::tr("Dose (Gy)"); + labelHeaderTitle = msg; + } + break; + case vtkMRMLIsodoseNode::Unknown: + { + QString msg = useRelativeIsolevels ? QObject::tr("Relative Units (%)") : QObject::tr("Units (MU)"); + labelHeaderTitle = msg; + } + break; + case vtkMRMLIsodoseNode::Relative: + labelHeaderTitle = QObject::tr("Relative Dose (%)"); + break; + default: + break; + } + d->tableView_IsodoseLevels->model()->setHeaderData( 1, Qt::Horizontal, labelHeaderTitle); + + // Make sure the dose volume has an associated isodose color table node + vtkMRMLColorTableNode* selectedColorNode = (useRelativeIsolevels) ? + d->logic()->GetRelativeIsodoseColorTable(this->mrmlScene()) + : + d->logic()->GetDefaultIsodoseColorTable(this->mrmlScene()); + + // Show color table node associated to the dose volume + paramNode->DisableModifiedEventOn(); + paramNode->SetAndObserveColorTableNode(selectedColorNode); + paramNode->DisableModifiedEventOff(); + + d->tableView_IsodoseLevels->setMRMLColorNode(selectedColorNode); + // Set current number of isodose levels + bool wasBlocked = d->spinBox_NumberOfLevels->blockSignals(true); + if (selectedColorNode) + { + d->spinBox_NumberOfLevels->setValue(selectedColorNode->GetNumberOfColors()); + + qvtkConnect(selectedColorNode, vtkCommand::ModifiedEvent, + this, SLOT(updateScalarBarsFromSelectedColorTable())); + } + else + { + d->spinBox_NumberOfLevels->setValue(0); + } + d->spinBox_NumberOfLevels->blockSignals(wasBlocked); + // Update scalar bars + this->updateScalarBarsFromSelectedColorTable(); + // Update dose volume palette + d->logic()->UpdateDoseColorTableFromIsodose(paramNode); +} + +//----------------------------------------------------------------------------- +void qSlicerIsodoseModuleWidget::setReferenceDoseValue(double value) +{ + Q_D(qSlicerIsodoseModuleWidget); + + vtkMRMLIsodoseNode* paramNode = vtkMRMLIsodoseNode::SafeDownCast(d->MRMLNodeComboBox_ParameterSet->currentNode()); + if (!paramNode) + { + return; + } + + if (d->sliderWidget_ReferenceDose->maximum() > 0.) + { + double percentage = 200. * value / d->sliderWidget_ReferenceDose->maximum(); + d->label_PercentageOfMaxVolumeDose->setText(tr("%1 %").arg( percentage, 0, 'g', 4)); + } + else + { + d->label_PercentageOfMaxVolumeDose->setText(""); + } + + paramNode->DisableModifiedEventOn(); + switch (paramNode->GetDoseUnits()) + { + case vtkMRMLIsodoseNode::Gy: + case vtkMRMLIsodoseNode::Unknown: + paramNode->SetReferenceDoseValue(value); + break; + case vtkMRMLIsodoseNode::Relative: + default: + paramNode->SetReferenceDoseValue(-1.); + break; + } + paramNode->DisableModifiedEventOff(); +} + //----------------------------------------------------------------------------- QString qSlicerIsodoseModuleWidget::generateNewIsodoseLevel() const { @@ -569,10 +744,8 @@ void qSlicerIsodoseModuleWidget::setIsolineVisibility(bool visible) std::vector childItemIDs; shNode->GetItemChildren(isdoseFolderItemID, childItemIDs, false); - std::vector::iterator childIt; - for (childIt=childItemIDs.begin(); childIt!=childItemIDs.end(); ++childIt) + for (vtkIdType childItemID : childItemIDs) { - vtkIdType childItemID = (*childIt); vtkMRMLModelNode* modelNode = vtkMRMLModelNode::SafeDownCast(shNode->GetItemDataNode(childItemID)); modelNode->GetDisplayNode()->SetVisibility2D(visible); } @@ -614,10 +787,8 @@ void qSlicerIsodoseModuleWidget::setIsosurfaceVisibility(bool visible) std::vector childItemIDs; shNode->GetItemChildren(isdoseFolderItemID, childItemIDs, false); - std::vector::iterator childIt; - for (childIt=childItemIDs.begin(); childIt!=childItemIDs.end(); ++childIt) + for (vtkIdType childItemID : childItemIDs) { - vtkIdType childItemID = (*childIt); vtkMRMLModelNode* modelNode = vtkMRMLModelNode::SafeDownCast(shNode->GetItemDataNode(childItemID)); modelNode->GetDisplayNode()->SetVisibility(visible); } @@ -800,26 +971,35 @@ void qSlicerIsodoseModuleWidget::updateScalarBarsFromSelectedColorTable() int newNumberOfColors = selectedColorNode->GetNumberOfColors(); + vtkMRMLIsodoseNode::DoseUnitsType doseUnits = paramNode->GetDoseUnits(); + bool relativeRepresentation = paramNode->GetRelativeRepresentationFlag(); + // Get dose unit name and assemble scalar bar title - std::string doseUnitName(""); - vtkIdType doseShItemID = shNode->GetItemByDataNode(doseVolumeNode); - if (doseShItemID != vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID) + std::string scalarBarTitle("Isolevels"); + switch (doseUnits) + { + case vtkMRMLIsodoseNode::Gy: { - doseUnitName = shNode->GetAttributeFromItemAncestor( - doseShItemID, vtkSlicerRtCommon::DICOMRTIMPORT_DOSE_UNIT_NAME_ATTRIBUTE_NAME, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy()); + const char* str = relativeRepresentation ? " (%)" : " (Gy)"; + scalarBarTitle += std::string(str); } - std::string scalarBarTitle("Dose"); - if (!doseUnitName.empty()) + break; + case vtkMRMLIsodoseNode::Relative: + scalarBarTitle += std::string(" (%)"); + break; + case vtkMRMLIsodoseNode::Unknown: + default: { - scalarBarTitle += " (" + doseUnitName + ")"; + const char* str = relativeRepresentation ? " (%)" : " (MU)"; + scalarBarTitle += std::string(str); + } + break; } // Update all scalar bar actors - for (std::vector::iterator it = d->ScalarBarWidgets.begin(); - it != d->ScalarBarWidgets.end(); ++it) + for (vtkScalarBarWidget* scalarBarWidget : d->ScalarBarWidgets) { - vtkSlicerRTScalarBarActor* actor = vtkSlicerRTScalarBarActor::SafeDownCast( - (*it)->GetScalarBarActor() ); + vtkSlicerRTScalarBarActor* actor = vtkSlicerRTScalarBarActor::SafeDownCast( scalarBarWidget->GetScalarBarActor() ); // Update actor actor->UseAnnotationAsLabelOn(); // Needed each time @@ -832,6 +1012,6 @@ void qSlicerIsodoseModuleWidget::updateScalarBarsFromSelectedColorTable() actor->GetLookupTable()->SetAnnotation(colorIndex, vtkStdString(selectedColorNode->GetColorName(colorIndex))); } actor->SetTitle(scalarBarTitle.c_str()); - (*it)->Render(); + scalarBarWidget->Render(); } } diff --git a/Isodose/qSlicerIsodoseModuleWidget.h b/Isodose/qSlicerIsodoseModuleWidget.h index de154b388..df80e80c9 100644 --- a/Isodose/qSlicerIsodoseModuleWidget.h +++ b/Isodose/qSlicerIsodoseModuleWidget.h @@ -59,6 +59,8 @@ public slots: /// Process loaded scene void onSceneImportedEvent(); + /// Process is scene is closed + void onSceneClosedEvent(); /// Set current parameter node void setParameterNode(vtkMRMLNode *node); @@ -97,6 +99,12 @@ protected slots: /// Slot called on modify of the color table void updateScalarBarsFromSelectedColorTable(); + /// Slot setting type of isolevels representation + void setRelativeIsolevelsFlag(bool useRelativeIsolevels); + + /// Slot called to set reference dose value + void setReferenceDoseValue(double value); + protected: // Generates a new isodose level name QString generateNewIsodoseLevel() const; diff --git a/SlicerRtCommon/vtkSlicerRtCommon.cxx b/SlicerRtCommon/vtkSlicerRtCommon.cxx index 9d3ea0ab1..cf16b0a5f 100644 --- a/SlicerRtCommon/vtkSlicerRtCommon.cxx +++ b/SlicerRtCommon/vtkSlicerRtCommon.cxx @@ -87,6 +87,7 @@ const std::string vtkSlicerRtCommon::DICOMRTIMPORT_SOURCE_HIERARCHY_NODE_NAME_PO const std::string vtkSlicerRtCommon::DICOMRTIMPORT_BEAMMODEL_HIERARCHY_NODE_NAME_POSTFIX = "_Beams"; const char* vtkSlicerRtCommon::DEFAULT_DOSE_COLOR_TABLE_NAME = "Dose_ColorTable"; +const char* vtkSlicerRtCommon::RELATIVE_DOSE_COLOR_TABLE_NAME = "Dose_ColorTable_Relative"; //---------------------------------------------------------------------------- // Utility functions diff --git a/SlicerRtCommon/vtkSlicerRtCommon.h b/SlicerRtCommon/vtkSlicerRtCommon.h index 57505a79d..273330516 100644 --- a/SlicerRtCommon/vtkSlicerRtCommon.h +++ b/SlicerRtCommon/vtkSlicerRtCommon.h @@ -116,6 +116,7 @@ class VTK_SLICERRTCOMMON_EXPORT vtkSlicerRtCommon static const std::string DICOMRTIMPORT_BEAMMODEL_HIERARCHY_NODE_NAME_POSTFIX; static const char* DEFAULT_DOSE_COLOR_TABLE_NAME; + static const char* RELATIVE_DOSE_COLOR_TABLE_NAME; //---------------------------------------------------------------------------- // Utility functions