Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Initial support of the arc beam sequence #215

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Beams/Logic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set(${KIT}_INCLUDE_DIRECTORIES
${SlicerRtCommon_INCLUDE_DIRS}
${vtkSlicerBeamsModuleMRML_INCLUDE_DIRS}
${vtkSlicerSubjectHierarchyModuleLogic_INCLUDE_DIRS}
${vtkSlicerSequencesModuleMRML_INCLUDE_DIRS}
${vtkSlicerSequencesModuleLogic_INCLUDE_DIRS}
)

set(${KIT}_SRCS
Expand All @@ -26,6 +28,8 @@ set(${KIT}_TARGET_LIBRARIES
vtkSlicerRtCommon
vtkSlicerMarkupsModuleMRML
vtkSlicerSubjectHierarchyModuleLogic
vtkSlicerSequencesModuleMRML
vtkSlicerSequencesModuleLogic
${ITK_LIBRARIES}
${VTK_LIBRARIES}
)
Expand Down
273 changes: 273 additions & 0 deletions Beams/Logic/vtkSlicerBeamsModuleLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,20 @@
#include "vtkMRMLRTPlanNode.h"
#include "vtkMRMLRTBeamNode.h"

#include <vtkSlicerRtCommon.h>

// SubjectHierarchy includes
#include "vtkMRMLSubjectHierarchyConstants.h"
#include "vtkMRMLSubjectHierarchyNode.h"
#include "vtkSlicerSubjectHierarchyModuleLogic.h"

// Sequences inludes
#include <vtkMRMLSequenceBrowserNode.h>
#include <vtkMRMLSequenceNode.h>

// MRML includes
#include <vtkMRMLModelDisplayNode.h>
#include <vtkMRMLModelHierarchyNode.h>
#include <vtkMRMLScene.h>
#include <vtkMRMLLinearTransformNode.h>
#include <vtkMRMLMarkupsFiducialNode.h>
Expand Down Expand Up @@ -299,3 +312,263 @@ void vtkSlicerBeamsModuleLogic::ProcessMRMLNodesEvents(vtkObject* caller, unsign
}
}
}

//----------------------------------------------------------------------------
bool vtkSlicerBeamsModuleLogic::CreateArcBeamDynamicSequence(
double initialAngle, double finalAngle, double stepAngle, bool direction,
vtkMRMLRTPlanNode* planNode, vtkMRMLSequenceBrowserNode* beamSequenceBrowserNode,
vtkMRMLSequenceNode* beamSequenceNode, vtkMRMLSequenceNode* transformSequenceNode)
{
std::vector<double> angles;
if (vtkSlicerRtCommon::AreEqualWithTolerance(finalAngle, initialAngle))
{
return false;
}

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 += stepAngle)
{
angles.push_back(angle);
}
if (angles.back() < finalAngle)
{
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
{
for (double angle = initialAngle; angle <= 360.; angle += stepAngle)
{
angles.push_back(angle);
}
double startAngle = 1.;
if (angles.back() < 360.)
{
startAngle = angles.back() + stepAngle - 360.;
}
else if (angles.back() == 360.)
{
startAngle = stepAngle;
}

for (double angle = startAngle; angle <= finalAngle; angle += stepAngle)
{
angles.push_back(angle);
}
if (angles.back() < finalAngle)
{
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
{
for (double angle = initialAngle; angle >= 0.0; angle -= stepAngle)
{
angles.push_back(angle);
}
double startAngle = 359.;
if (angles.back() > 0.)
{
startAngle = angles.back() - stepAngle + 360.;
}
else if (angles.back() == 0.)
{
startAngle = 360. - stepAngle;
}
for (double angle = startAngle; angle >= finalAngle; angle -= stepAngle)
{
angles.push_back(angle);
}
if (angles.back() > finalAngle)
{
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
{
for (double angle = initialAngle; angle >= finalAngle; angle -= stepAngle)
{
angles.push_back(angle);
}
if (angles.back() > finalAngle)
{
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");
return false;
}

vtkMRMLScene* scene = planNode->GetScene();

vtkMRMLSubjectHierarchyNode* shNode = vtkMRMLSubjectHierarchyNode::GetSubjectHierarchyNode(scene);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scene->GetSubjectHierarchyNode() instead please

if (!shNode)
{
vtkErrorMacro("CreateArcBeamDynamicSequence: Failed to access subject hierarchy node");
return false;
}
vtkSmartPointer<vtkMRMLModelHierarchyNode> beamModelHierarchyRootNode;

const char* beamName = "Beam [ArcDelivery]";
// Create sequence node for RTBeam, transformation, table

// beamSequenceNode
beamSequenceNode->SetName(beamName);
beamSequenceNode->SetIndexName("Control point");
beamSequenceNode->SetIndexUnit("index");
beamSequenceNode->SetIndexType(vtkMRMLSequenceNode::NumericIndex);

// transformSequenceNode
std::string name = std::string(beamName) + vtkMRMLRTBeamNode::BEAM_TRANSFORM_NODE_NAME_POSTFIX;
transformSequenceNode->SetName(name.c_str());
transformSequenceNode->SetIndexName("Control point");
transformSequenceNode->SetIndexUnit("index");
transformSequenceNode->SetIndexType(vtkMRMLSequenceNode::NumericIndex);

// beamSequenceBrowserNode
name = std::string(beamName) + "_SequenceBrowser";
beamSequenceBrowserNode->SetName(name.c_str());

// Add sequence nodes to the scene
scene->AddNode(beamSequenceNode);
scene->AddNode(transformSequenceNode);
scene->AddNode(beamSequenceBrowserNode);

for ( auto it = angles.begin(); it != angles.end(); ++it)
{
unsigned int controlPointIndex = it - angles.begin();
// Create the beam node for each control point
vtkSmartPointer<vtkMRMLRTBeamNode> beamNode = vtkSmartPointer<vtkMRMLRTBeamNode>::New();

std::ostringstream nameStream;
nameStream << beamName << " [Dynamic] : CP" << controlPointIndex;

std::string newBeamName = nameStream.str();
beamNode->SetName(newBeamName.c_str());

// Set beam geometry parameters from DICOM
double jawPositions[2][2] = {{-40.0, 40.0},{-40.0, 40.0}};
beamNode->SetX1Jaw(jawPositions[0][0]);
beamNode->SetX2Jaw(jawPositions[0][1]);
beamNode->SetY1Jaw(jawPositions[1][0]);
beamNode->SetY2Jaw(jawPositions[1][1]);

beamNode->SetGantryAngle(*it);

// SAD for RTPlan, source to beam limiting devices (Jaws, MLC)
if (beamNode)
{
// beamNode->SetSAD(rtReader->GetBeamSourceAxisDistance(dicomBeamNumber));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these commented out? If you want to keep them there please add a TODO

// beamNode->SetSourceToJawsDistanceX(rtReader->GetBeamSourceToJawsDistanceX(dicomBeamNumber));
// beamNode->SetSourceToJawsDistanceY(rtReader->GetBeamSourceToJawsDistanceY(dicomBeamNumber));
// beamNode->SetSourceToMultiLeafCollimatorDistance(rtReader->GetBeamSourceToMultiLeafCollimatorDistance(dicomBeamNumber));
}

// Add beam to beam sequence node
if (beamNode)
{
beamSequenceNode->SetDataNodeAtValue( beamNode, std::to_string(controlPointIndex));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it some new convention that there is a space after the opening parentheses but none before the closing one? If there is on good reason for this please use the style that is used everywhere else in SlicerRT.

}

// Get or create RTBeam transformation from RTPlan without adding beam to the plan
// Add beam transformation to transform sequence
vtkMRMLLinearTransformNode* transformNode = beamNode->CreateBeamTransformNode(scene);
if (transformNode)
{
double isocenter[3] = { 0., 0., 0.};
planNode->GetIsocenterPosition(isocenter);
{
// Update beam transform without translation to isocenter
this->UpdateTransformForBeam( beamSequenceNode->GetSequenceScene(), beamNode, transformNode, isocenter);

vtkTransform* transform = vtkTransform::SafeDownCast(transformNode->GetTransformToParent());
if (isocenter)
{
// Actual translation to isocenter
transform->Translate( isocenter[0], isocenter[1], isocenter[2]);
transformNode->Modified();
}

transformSequenceNode->SetDataNodeAtValue( transformNode, std::to_string(controlPointIndex));
}
}
} // end of a control point

// Synchronize beam sequence, beam transform sequence, table sequence nodes
beamSequenceBrowserNode->SetAndObserveMasterSequenceNodeID(beamSequenceNode->GetID());
beamSequenceBrowserNode->AddSynchronizedSequenceNode(transformSequenceNode);

// Get proxy beam node
vtkMRMLNode* node = beamSequenceBrowserNode->GetProxyNode(beamSequenceNode);
if (vtkMRMLRTBeamNode::SafeDownCast(node))
{
return true;
}
else
{
return false;
}
}
16 changes: 16 additions & 0 deletions Beams/Logic/vtkSlicerBeamsModuleLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "vtkMRMLRTBeamNode.h"

class vtkSlicerMLCPositionLogic;
class vtkMRMLSequenceBrowserNode;
class vtkMRMLSequenceNode;

/// \ingroup SlicerRt_QtModules_Beams
class VTK_SLICER_BEAMS_LOGIC_EXPORT vtkSlicerBeamsModuleLogic :
Expand All @@ -60,6 +62,20 @@ class VTK_SLICER_BEAMS_LOGIC_EXPORT vtkSlicerBeamsModuleLogic :
void UpdateTransformForBeam( vtkMRMLScene* beamSequenceScene, vtkMRMLRTBeamNode* beamNode,
vtkMRMLLinearTransformNode* beamTransformNode, double isocenter[3]);

/// Create arc delivery beam sequence
/// @param initialAngle - initial angle in degrees
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the docstring convention that is used everywhere in Slicer and do not introduce another one. Not just here but everywhere and in the other PR as well please.

/// @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);

protected:
vtkSlicerBeamsModuleLogic();
~vtkSlicerBeamsModuleLogic() override;
Expand Down
6 changes: 0 additions & 6 deletions Beams/MRML/vtkMRMLRTBeamNode.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -916,9 +916,3 @@ void vtkMRMLRTBeamNode::CreateMLCPointsFromSectionBorder( double jawBegin,
}
side12.push_back(side2.back());
}

//----------------------------------------------------------------------------
bool vtkMRMLRTBeamNode::AreEqual( double v1, double v2)
{
return vtkSlicerRtCommon::AreEqualWithTolerance( v1, v2);
}
2 changes: 0 additions & 2 deletions Beams/MRML/vtkMRMLRTBeamNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,6 @@ class VTK_SLICER_BEAMS_MODULE_MRML_EXPORT vtkMRMLRTBeamNode : public vtkMRMLMode
void CreateMLCPointsFromSectionBorder( double jawBegin, double jawEnd,
bool mlcType, const MLCSectionVector::value_type& sectionBorder,
MLCVisiblePointVector& side12);

static bool AreEqual( double v1, double v2);
};

#endif // __vtkMRMLRTBeamNode_h
2 changes: 1 addition & 1 deletion Beams/Widgets/Resources/UI/qMRMLBeamParametersTabWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<string>TabWidget</string>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="tabGeometry">
<attribute name="title">
Expand Down
7 changes: 3 additions & 4 deletions Beams/qSlicerBeamsModule.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ QStringList qSlicerBeamsModule::dependencies()const
//-----------------------------------------------------------------------------
QString qSlicerBeamsModule::helpText()const
{
QString help =
"This module displays and handles beam geometry models created from the loaded isocenter and source fiducials. "
"For more information see <a href=\"%1/Documentation/%2.%3/Modules/Beams\">%1/Documentation/%2.%3/Modules/Beams</a><br>";
return help.arg(this->slicerWikiUrl()).arg(Slicer_VERSION_MAJOR).arg(Slicer_VERSION_MINOR);
return QString("This module displays and handles beam geometry models created from the loaded isocenter and source fiducials. "
"For more information see <a href=\"%1/Documentation/%2.%3/Modules/Beams\">%1/Documentation/%2.%3/Modules/Beams</a><br>").arg(
this->slicerWikiUrl()).arg(qSlicerCoreApplication::application()->majorVersion()).arg(qSlicerCoreApplication::application()->minorVersion());
}

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