From 0a3415cdf2b5f1b7096dcd53aa086648cd11349b Mon Sep 17 00:00:00 2001 From: Mikhail Polkovnikov Date: Thu, 4 Aug 2022 14:48:41 +0300 Subject: [PATCH] ENH: Dynamic arc beam sequence GUI and logic upgrades 1. Logic debug information was removed 2. ExternalBeamPlanning module description file was created --- Beams/Logic/vtkSlicerBeamsModuleLogic.cxx | 64 +--------- Beams/Logic/vtkSlicerBeamsModuleLogic.h | 30 ++--- .../Logic/vtkSlicerDicomRtReader.cxx | 5 +- .../Logic/vtkSlicerDicomRtReader.h | 1 + .../modules/externalbeamplanning.md | 101 +++++++++++++++ .../UI/qSlicerExternalBeamPlanningModule.ui | 116 +++++++++--------- .../Widgets/qSlicerDoseEngineLogic.cxx | 9 ++ .../Widgets/qSlicerDoseEngineLogic.h | 12 +- ...SlicerExternalBeamPlanningModuleWidget.cxx | 4 +- 9 files changed, 200 insertions(+), 142 deletions(-) create mode 100644 Docs/user_guide/modules/externalbeamplanning.md diff --git a/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx b/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx index f58696c4..350ef2ff 100644 --- a/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx +++ b/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx @@ -340,9 +340,6 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( { angles.push_back(finalAngle); } - std::cout << "Angles " << angles.size() << '\n'; - std::for_each( angles.begin(), angles.end(), [](double v){ std::cout << v << ' '; }); - std::cout << std::endl; } else if (!direction && initialAngle > finalAngle) // CW, ini > fin { @@ -368,9 +365,6 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( { angles.push_back(finalAngle); } - std::cout << "Angles " << angles.size() << '\n'; - std::for_each( angles.begin(), angles.end(), [](double v){ std::cout << v << ' '; }); - std::cout << std::endl; } else if (direction && initialAngle < finalAngle) // CCW, ini < fin { @@ -395,9 +389,6 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( { angles.push_back(finalAngle); } - std::cout << "Angles " << angles.size() << '\n'; - std::for_each( angles.begin(), angles.end(), [](double v){ std::cout << v << ' '; }); - std::cout << std::endl; } else if (direction && initialAngle > finalAngle) // CCW, ini > fin { @@ -409,53 +400,7 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( { angles.push_back(finalAngle); } - std::cout << "Angles " << angles.size() << '\n'; - std::for_each( angles.begin(), angles.end(), [](double v){ std::cout << v << ' '; }); - std::cout << std::endl; } -/* - if (finalAngle < 0. && finalAngle > 360. && initialAngle < 0. && initialAngle > 360.) - { - return false; - } - - if (!direction && initialAngle < finalAngle) // CW, ini < fin - { - for (double angle = initialAngle; angle <= finalAngle; angle += 1.) - { - angles.push_back(angle); - } - } - else if (!direction && initialAngle > finalAngle) // CW, ini > fin - { - for (double angle = initialAngle; angle <= 360.; angle += 1.) - { - angles.push_back(angle); - } - for (double angle = 1.; angle <= finalAngle; angle += 1.) - { - angles.push_back(angle); - } - } - else if (direction && initialAngle < finalAngle) // CCW, ini < fin - { - for (double angle = initialAngle; angle >= 0.0; angle -= 1.) - { - angles.push_back(angle); - } - for (double angle = 359.; angle >= finalAngle; angle -= 1.) - { - angles.push_back(angle); - } - } - else if (direction && initialAngle > finalAngle) // CCW, ini > fin - { - for (double angle = initialAngle; angle >= finalAngle; angle -= 1.) - { - angles.push_back(angle); - } - } -*/ if (angles.size() < 2) { vtkErrorMacro("CreateArcBeamDynamicSequence: Number of angle elements is less than 2"); @@ -464,7 +409,7 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( vtkMRMLScene* scene = planNode->GetScene(); - vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(scene); + vtkMRMLSubjectHierarchyNode* shNode = scene->GetSubjectHierarchyNode(); if (!shNode) { vtkErrorMacro("CreateArcBeamDynamicSequence: Failed to access subject hierarchy node"); @@ -521,10 +466,7 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( // SAD for RTPlan, source to beam limiting devices (Jaws, MLC) if (beamNode) { -// beamNode->SetSAD(rtReader->GetBeamSourceAxisDistance(dicomBeamNumber)); -// beamNode->SetSourceToJawsDistanceX(rtReader->GetBeamSourceToJawsDistanceX(dicomBeamNumber)); -// beamNode->SetSourceToJawsDistanceY(rtReader->GetBeamSourceToJawsDistanceY(dicomBeamNumber)); -// beamNode->SetSourceToMultiLeafCollimatorDistance(rtReader->GetBeamSourceToMultiLeafCollimatorDistance(dicomBeamNumber)); +// TODO: Add beam parameters for each angle step } // Add beam to beam sequence node @@ -545,7 +487,7 @@ bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence( this->UpdateTransformForBeam( beamSequenceNode->GetSequenceScene(), beamNode, transformNode, isocenter); vtkTransform* transform = vtkTransform::SafeDownCast(transformNode->GetTransformToParent()); - if (isocenter) + if (transform) { // Actual translation to isocenter transform->Translate( isocenter[0], isocenter[1], isocenter[2]); diff --git a/Beams/Logic/vtkSlicerBeamsModuleLogic.h b/Beams/Logic/vtkSlicerBeamsModuleLogic.h index 8869840f..80759514 100644 --- a/Beams/Logic/vtkSlicerBeamsModuleLogic.h +++ b/Beams/Logic/vtkSlicerBeamsModuleLogic.h @@ -54,24 +54,24 @@ class VTK_SLICER_BEAMS_LOGIC_EXPORT vtkSlicerBeamsModuleLogic : /// Update parent transform of a given beam using its parameters and the IEC logic /// without using plan node (only isocenter position) - /// @param beamSequenceScene - inner scene of the beam sequence node - /// @param beamNode - a current beam node (must be added to the beam sequence node beforehand) - /// @param beamTransformNode - parent transform of the beam according to the beam parameters and isocenter - /// @param isocenter - isocenter position + /// \param beamSequenceScene inner scene of the beam sequence node + /// \param beamNode a current beam node (must be added to the beam sequence node beforehand) + /// \param beamTransformNode parent transform of the beam according to the beam parameters and isocenter + /// \param isocenter isocenter position /// \warning This method is used only in vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::LoadDynamicBeamSequence - void UpdateTransformForBeam( vtkMRMLScene* beamSequenceScene, vtkMRMLRTBeamNode* beamNode, + void UpdateTransformForBeam(vtkMRMLScene* beamSequenceScene, vtkMRMLRTBeamNode* beamNode, vtkMRMLLinearTransformNode* beamTransformNode, double isocenter[3]); /// Create arc delivery beam sequence - /// @param initialAngle - initial angle in degrees - /// @param finalAngle - final angle in degrees - /// @param angleStep - single angle step within arc - /// @param direction - 0 - clockwise, 1 - counter-clockwise - /// @param planNode - input plan node, which contains reference volume node and isocenter position - /// @param sequenceBrowserNode - output sequence browser node - /// @param sequenceBeamNode - output beam node - /// @param sequenceTransformNode - output transform node - bool CreateArcBeamDynamicSequence( double initialAngle, double finalAngle, + /// \param initialAngle initial angle in degrees + /// \param finalAngle final angle in degrees + /// \param angleStep single angle step within arc + /// \param direction 0 - clockwise, 1 - counter-clockwise + /// \param planNode input plan node, which contains reference volume node and isocenter position + /// \param sequenceBrowserNode output sequence browser node + /// \param sequenceBeamNode output beam node + /// \param sequenceTransformNode output transform node + bool CreateArcBeamDynamicSequence(double initialAngle, double finalAngle, double angleStep, bool direction, vtkMRMLRTPlanNode* planNode, vtkMRMLSequenceBrowserNode* beamSequenceBrowserNode, vtkMRMLSequenceNode* beamSequenceNode, vtkMRMLSequenceNode* transformSequenceNode); @@ -95,7 +95,7 @@ class VTK_SLICER_BEAMS_LOGIC_EXPORT vtkSlicerBeamsModuleLogic : vtkSlicerBeamsModuleLogic(const vtkSlicerBeamsModuleLogic&) = delete; void operator=(const vtkSlicerBeamsModuleLogic&) = delete; - vtkSlicerMLCPositionLogic* MLCPositionLogic; + vtkSlicerMLCPositionLogic* MLCPositionLogic{ nullptr }; }; #endif diff --git a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx index 48ab5c4c..dcc2b1c1 100644 --- a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx +++ b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx @@ -319,7 +319,7 @@ class vtkSlicerDicomRtReader::vtkInternal std::string InstitutionalDepartmentName; std::string ManufacturerModelName; }; - + /// List of loaded beams from external beam plan std::vector BeamSequenceVector; @@ -589,9 +589,8 @@ bool vtkSlicerDicomRtReader::vtkInternal::BeamEntry::IsArcDeliverySequence( (cp1.GantryAngle >= 0. && cp1.GantryAngle <= 360.); res = point0 && point1; - if (point0 && point1) + if (res) { - res = true; initialAngle = cp0.GantryAngle; finalAngle = cp1.GantryAngle; // rotation: 0 - clockwise, 1 - counter clockwise diff --git a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h index 9aafd224..af92e598 100644 --- a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h +++ b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h @@ -37,6 +37,7 @@ // STD includes #include +#include class vtkPolyData; diff --git a/Docs/user_guide/modules/externalbeamplanning.md b/Docs/user_guide/modules/externalbeamplanning.md new file mode 100644 index 00000000..904ab4cf --- /dev/null +++ b/Docs/user_guide/modules/externalbeamplanning.md @@ -0,0 +1,101 @@ +## "External Beam Planning" module description + +The ExternalBeamPlanning module is a generic, extensible module for forward +planning of external beam radiation therapy treatments. + +The "External Beam Planning" module is responsible for creating a beam plan or ion beam plan +according to DICOM RT standard. Only a few basic features are supported. +Each plan consists of a number of beams: static or dynamic. Each plan supports +only one isocenter point, multiple isocenters per a single plan are not allowed. + +This work was in part funded by An Applied Cancer Research Unit of Cancer Care Ontario +with funds provided by the Ministry of Health and Long-Term Care and the Ontario Consortium +for Adaptive Interventions in Radiation Oncology (OCAIRO) to provide free, open-source toolset +for radiotherapy and related image-guided interventions. + +Author: Csaba Pinter (PerkLab, Queen's University), Greg Sharp (Massachusetts General Hospital) +Contact: Csaba Pinter, csaba.pinter@queensu.ca + +![image](https://www.slicer.org/w/img_auth.php/3/3f/LogoCco.png) +![image](https://www.slicer.org/w/img_auth.php/2/27/LogoOCAIRO.jpg) + +### Use Cases +- Proton dose calculation +- Any dose engine can be integrated (C++, Python, Matlab) + +### Tutorials +[Orthovoltage RT treatment planning tutorial](https://github.com/SlicerRt/SlicerRtDoc/blob/master/tutorials/SlicerRT_Tutorial_OrthovoltageDoseEngine.pptx) (uses EGSnrc) + +### Graphical User Interface loadable module (GUI). Panels and their use + +![image](https://user-images.githubusercontent.com/3785912/183899734-d4795313-fd39-4c40-8df6-e84f0469c3b0.png) + +Loadable GUI module "External Beam Planning" is used to setup beam parameters +for beams or ion beams, or modify already created beams. +Static RT beam is setup by: Isocenter position, SAD, SID, Jaws borders, MLC positions. Static beam is fixed. + +Dynamic RT and Ion RT beams are setup by: initial and final angles of rotation around gantry axis, +direction of rotation, isocenter position, SAD, SID, Jaws borders, MLC positions, scanning spot map, etc. +Dynamic beam changes it position over time by means of rotation or (and) changing the position of phase space of the beam. + +#### Active RT plan + +![image](https://user-images.githubusercontent.com/3785912/183899795-762b8b13-4ad5-4112-bcb6-e3f5fbd21fe2.png) + +1. **Active RT plan**: Selected RT Plan +2. **Ion plan**: flag to generate RTIonPlan instead of RTPlan + +#### Plan parameters + +![image](https://user-images.githubusercontent.com/3785912/183899832-fb178c71-900f-48d8-97a5-35c4d532bfb3.png) + +1. **Reference volume**: Reference volume node data (usually CT) +2. **Structure set**: Segmentation node data +3. **Points of interest**: Markups fiducial to be used as isocenter +4. **Isocenter**: Isocenter position in RAS +5. **Center views**: Center slice views on isocenter +6. **Target volume**: Select targer ROI +7. **Isocenter at target center**: Set isocenter in the center of the targer ROI +8. **Dose engine**: Select a dose engine calculator **Not implemented** +9. **Rx Dose (Gy)**: Setup an amount of dose in Gy + +#### Output + +![image](https://user-images.githubusercontent.com/3785912/183899869-580d00a9-aa86-405d-b22c-d11a35f2f6df.png) + +1. **Output dose volume**: Setup output dose volume +2. **Clear dose**: Clear dose volume +3. **Calculate WED**: Calculate water-equivalent distance **Not implemented** +4. **Calculate Dose**: Calculate dose according to the engine parameters **Not implemented** + +#### Beams + +![image](https://user-images.githubusercontent.com/3785912/183899904-6f8e76fd-1574-462e-bf31-21f2cf5fa740.png) + +1. **Arc beam sequence**: Activate dynamic beam arc movement around gantry rotation axis +2. **Initial angle**: Initial angle position in degrees +3. **Final angle**: Final angle position in degrees +4. **Angle step**: Angle step +5. **Rotation direction**: Clockwise (CW) of CounterClockwise (CCW) rotation +5. **Add beam**: Add static or dynamic arc beam to the plan +6. **Remove beam**: Remove selected beam from the plan + +### Arc beam + +The beam movement around rotation axis could be used to calculate a filtered back-projection reconstruction for cone-beam geometries. +For fast forward and back projection reconstruction the [RTK](https://www.openrtk.org) library can be used. + +### References + +1. Sharp, G., Pinter, C., Unkelbach, J., Fichtinger, G. (2017). Open Source Proton Treatment Planning in 3D Slicer: Status Update. Proceedings to the 56 Annual Meeting of the Particle Therapy Cooperative Group (PTCOG), 8-13 May 2017. International Journal of Particle Therapy: Summer 2017, Vol. 4, No. 1, pp. 14-83. + +### Information for Developers + +- Sample C++ dose engine: [https://github.com/SlicerRt/SlicerRT/blob/master/ExternalBeamPlanning/Widgets/qSlicerMockDoseEngine.h](https://github.com/SlicerRt/SlicerRT/blob/master/ExternalBeamPlanning/Widgets/qSlicerMockDoseEngine.h) +- Sample Python dose engine: [https://github.com/SlicerRt/SlicerRT/blob/master/ExternalBeamPlanning/Widgets/Python/MockPythonDoseEngine.py](https://github.com/SlicerRt/SlicerRT/blob/master/ExternalBeamPlanning/Widgets/Python/MockPythonDoseEngine.py) +- Future plans + - Inverse planning capabilities + - Matlab plugin adapter + - Ports module (apertures, MLC, target volume) + - Beam groups (common parameters for a group of beams) + diff --git a/ExternalBeamPlanning/Resources/UI/qSlicerExternalBeamPlanningModule.ui b/ExternalBeamPlanning/Resources/UI/qSlicerExternalBeamPlanningModule.ui index 28187a7c..a2b93944 100644 --- a/ExternalBeamPlanning/Resources/UI/qSlicerExternalBeamPlanningModule.ui +++ b/ExternalBeamPlanning/Resources/UI/qSlicerExternalBeamPlanningModule.ui @@ -6,8 +6,8 @@ 0 0 - 483 - 704 + 485 + 787 @@ -489,58 +489,7 @@ 4 - - - - - - - 112 - 0 - - - - Add beam - - - - :/Icons/Add.png:/Icons/Add.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 112 - 0 - - - - Remove beam - - - - :/Icons/Remove.png:/Icons/Remove.png - - - - - - + @@ -551,7 +500,7 @@ - + Arc beam sequence @@ -663,6 +612,57 @@ + + + + + + + 112 + 0 + + + + Add beam + + + + :/Icons/Add.png:/Icons/Add.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 112 + 0 + + + + Remove beam + + + + :/Icons/Remove.png:/Icons/Remove.png + + + + + @@ -675,6 +675,12 @@
ctkCollapsibleButton.h
1 + + ctkCollapsibleGroupBox + QGroupBox +
ctkCollapsibleGroupBox.h
+ 1 +
ctkCoordinatesWidget QWidget diff --git a/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.cxx b/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.cxx index 9844ed3e..9230588e 100644 --- a/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.cxx +++ b/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.cxx @@ -534,6 +534,15 @@ vtkMRMLRTBeamNode* qSlicerDoseEngineLogic::createArcBeamInPlan( vtkMRMLRTPlanNod proxyBeamNode->SetAndObserveTransformNodeID(proxyTransformNode->GetID()); return proxyBeamNode; } + else + { + qWarning() << Q_FUNC_INFO << ": Proxy beam node or proxy transform node in beam sequence is invalid"; + } } + else + { + qWarning() << Q_FUNC_INFO << ": Unable to create arc beam sequence"; + } + return nullptr; } diff --git a/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.h b/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.h index e3a9f3bb..3f0cc841 100644 --- a/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.h +++ b/ExternalBeamPlanning/Widgets/qSlicerDoseEngineLogic.h @@ -70,14 +70,14 @@ class Q_SLICER_MODULE_EXTERNALBEAMPLANNING_WIDGETS_EXPORT qSlicerDoseEngineLogic Q_INVOKABLE void removeIntermediateResults(vtkMRMLRTPlanNode* planNode); /// Create a beam for a plan (with beam parameters defined by the dose engine of the plan) - /// @param planNode - node of the current plan + /// \param planNode node of the current plan Q_INVOKABLE vtkMRMLRTBeamNode* createBeamInPlan(vtkMRMLRTPlanNode* planNode); /// Create a beam for a RT plan (with beam parameters defined by the dose engine of the plan) - /// @param planNode - node of the current plan - /// @param initialAngle - initial angle - /// @param finalAngle - final angle - /// @param stepAngle - step angle - /// @param direction - rotation direction (0 == CW, 1 == CCW) + /// \param planNode node of the current plan + /// \param initialAngle initial angle + /// \param finalAngle final angle + /// \param stepAngle step angle + /// \param direction rotation direction (0 == CW, 1 == CCW) Q_INVOKABLE vtkMRMLRTBeamNode* createArcBeamInPlan(vtkMRMLRTPlanNode* planNode, double initialAngle = 0., double finalAngle = 360., double stepAngle = 1., bool rotationDirection = false); diff --git a/ExternalBeamPlanning/qSlicerExternalBeamPlanningModuleWidget.cxx b/ExternalBeamPlanning/qSlicerExternalBeamPlanningModuleWidget.cxx index 3e92a366..6582a04f 100644 --- a/ExternalBeamPlanning/qSlicerExternalBeamPlanningModuleWidget.cxx +++ b/ExternalBeamPlanning/qSlicerExternalBeamPlanningModuleWidget.cxx @@ -915,7 +915,7 @@ void qSlicerExternalBeamPlanningModuleWidget::addBeamClicked() } vtkMRMLRTBeamNode* beamNode = nullptr; - if (!d->GroupBox_ArcBeamSequence->isChecked()) // Static beam + if (!d->CollapsibleGroupBox_ArcBeamSequence->isChecked()) // Static beam { // Create new beam node by replicating currently selected beam beamNode = d->DoseEngineLogic->createBeamInPlan(planNode); @@ -925,7 +925,7 @@ void qSlicerExternalBeamPlanningModuleWidget::addBeamClicked() return; } } - else // Dynamic beam sequence + else // Arc beam sequence { QApplication::setOverrideCursor(Qt::WaitCursor); int direction = -1;