diff --git a/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx b/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx index 772b1bd10..b24cf0398 100644 --- a/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx +++ b/Beams/Logic/vtkSlicerBeamsModuleLogic.cxx @@ -30,6 +30,7 @@ #include #include #include +#include // VTK includes #include @@ -105,6 +106,12 @@ void vtkSlicerBeamsModuleLogic::OnMRMLSceneNodeAdded(vtkMRMLNode* node) events->InsertNextValue(vtkMRMLRTPlanNode::BeamAdded); vtkObserveMRMLNodeEventsMacro(node, events); } + else if (node->IsA("vtkMRMLTableNode")) + { + vtkSmartPointer events = vtkSmartPointer::New(); + events->InsertNextValue(vtkCommand::ModifiedEvent); + vtkObserveMRMLNodeEventsMacro(node, events); + } } //--------------------------------------------------------------------------- @@ -224,4 +231,27 @@ void vtkSlicerBeamsModuleLogic::ProcessMRMLNodesEvents(vtkObject* caller, unsign beamNode->UpdateGeometry(); } } + else if (caller->IsA("vtkMRMLTableNode")) + { + vtkMRMLTableNode* tableNode = vtkMRMLTableNode::SafeDownCast(caller); + if (event == vtkCommand::ModifiedEvent) + { + // Iterate through all beam nodes + std::vector beamNodes; + mrmlScene->GetNodesByClass("vtkMRMLRTBeamNode", beamNodes); + for (std::vector::iterator beamIterator = beamNodes.begin(); + beamIterator != beamNodes.end(); ++beamIterator) + { + // if caller node and referenced table node is the same + // update beam polydata + vtkMRMLRTBeamNode* beamNode = vtkMRMLRTBeamNode::SafeDownCast(*beamIterator); + vtkMRMLTableNode* beamMLCTableNode = beamNode->GetMLCPositionTableNode(); + if (beamMLCTableNode == tableNode) + { + // Update beam model + beamNode->UpdateGeometry(); + } + } + } + } } diff --git a/Beams/MRML/vtkMRMLRTBeamNode.cxx b/Beams/MRML/vtkMRMLRTBeamNode.cxx index fe3a8c893..425f2872a 100644 --- a/Beams/MRML/vtkMRMLRTBeamNode.cxx +++ b/Beams/MRML/vtkMRMLRTBeamNode.cxx @@ -23,29 +23,43 @@ #include "vtkMRMLRTBeamNode.h" #include "vtkMRMLRTPlanNode.h" +// SlicerRT includes +#include "vtkSlicerRtCommon.h" + // MRML includes #include #include #include +#include #include #include #include #include // VTK includes +#include #include #include #include #include #include #include +#include #include +//------------------------------------------------------------------------------ +namespace +{ + const size_t MLCX_STRING_SIZE = strlen("MLCX"); + const size_t MLCY_STRING_SIZE = strlen("MLCY"); +} // namespace + //------------------------------------------------------------------------------ const char* vtkMRMLRTBeamNode::NEW_BEAM_NODE_NAME_PREFIX = "NewBeam_"; const char* vtkMRMLRTBeamNode::BEAM_TRANSFORM_NODE_NAME_POSTFIX = "_BeamTransform"; //------------------------------------------------------------------------------ +static const char* MLCBOUNDARY_REFERENCE_ROLE = "MLCBoundaryRef"; static const char* MLCPOSITION_REFERENCE_ROLE = "MLCPositionRef"; static const char* DRR_REFERENCE_ROLE = "DRRRef"; static const char* CONTOUR_BEV_REFERENCE_ROLE = "contourBEVRef"; @@ -293,13 +307,33 @@ void vtkMRMLRTBeamNode::CreateNewBeamTransformNode() } //---------------------------------------------------------------------------- -vtkMRMLDoubleArrayNode* vtkMRMLRTBeamNode::GetMLCPositionDoubleArrayNode() +vtkMRMLDoubleArrayNode* vtkMRMLRTBeamNode::GetMLCBoundaryDoubleArrayNode() +{ + return vtkMRMLDoubleArrayNode::SafeDownCast( this->GetNodeReference(MLCBOUNDARY_REFERENCE_ROLE) ); +} + +//---------------------------------------------------------------------------- +void vtkMRMLRTBeamNode::SetAndObserveMLCBoundaryDoubleArrayNode(vtkMRMLDoubleArrayNode* node) +{ + if (node && this->Scene != node->GetScene()) + { + vtkErrorMacro("Cannot set reference: the referenced and referencing node are not in the same scene"); + return; + } + + this->SetNodeReferenceID(MLCBOUNDARY_REFERENCE_ROLE, (node ? node->GetID() : nullptr)); + + this->InvokeCustomModifiedEvent(vtkMRMLRTBeamNode::BeamGeometryModified); +} + +//---------------------------------------------------------------------------- +vtkMRMLTableNode* vtkMRMLRTBeamNode::GetMLCPositionTableNode() { - return vtkMRMLDoubleArrayNode::SafeDownCast( this->GetNodeReference(MLCPOSITION_REFERENCE_ROLE) ); + return vtkMRMLTableNode::SafeDownCast( this->GetNodeReference(MLCPOSITION_REFERENCE_ROLE) ); } //---------------------------------------------------------------------------- -void vtkMRMLRTBeamNode::SetAndObserveMLCPositionDoubleArrayNode(vtkMRMLDoubleArrayNode* node) +void vtkMRMLRTBeamNode::SetAndObserveMLCPositionTableNode(vtkMRMLTableNode* node) { if (node && this->Scene != node->GetScene()) { @@ -480,94 +514,283 @@ void vtkMRMLRTBeamNode::CreateBeamPolyData(vtkPolyData* beamModelPolyData/*=null vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer cellArray = vtkSmartPointer::New(); - vtkMRMLDoubleArrayNode* mlcArrayNode = this->GetMLCPositionDoubleArrayNode(); - if (mlcArrayNode) + vtkMRMLTableNode* mlcTableNode = nullptr; + + vtkIdType nofLeaves = 0; + + // MLC boundary data + vtkMRMLDoubleArrayNode* arrayNode = this->GetMLCBoundaryDoubleArrayNode(); + std::vector mlcBounds; + if (arrayNode) + { + vtkDoubleArray* array = arrayNode->GetArray(); + nofLeaves = array->GetNumberOfTuples(); // real number of leaves plus 1 + for ( vtkIdType i = 0; i < nofLeaves; ++i) + { + mlcBounds.push_back(array->GetTuple1(i)); + } + nofLeaves--; // real number of leaves + } + + if (nofLeaves) + { + mlcTableNode = this->GetMLCPositionTableNode(); + if (!mlcTableNode || (mlcTableNode->GetNumberOfRows() != nofLeaves)) + { + vtkErrorMacro("CreateBeamPolyData: Invalid MLC nodes, or " \ + "number of MLC boundaries and positions are different"); + mlcTableNode = nullptr; + arrayNode = nullptr; + return; + } + } + + bool xOpened = !vtkSlicerRtCommon::AreEqualWithTolerance( this->X2Jaw, this->X1Jaw); + bool yOpened = !vtkSlicerRtCommon::AreEqualWithTolerance( this->Y2Jaw, this->Y1Jaw); + + // Check that we have MLC with Jaws opening + if (mlcTableNode && xOpened && yOpened) { - // First we extract the shape of the MLC - int y2count = -(this->Y2Jaw/10.0); - int y1count = -(this->Y1Jaw/10.0); - int numLeavesVisible = y1count - y2count; // Calculate the number of leaves visible - int numPointsEachSide = numLeavesVisible *2; + typedef std::vector< std::pair< double, double > > MLCType; + + MLCType mlcBoundary; // temporary MLC Boundaries vector + MLCType mlcPosition; // temporary MLC Positions vector + MLCType s1, s2; // real points for side "1" and "2" + + vtkTable* table = mlcTableNode->GetTable(); - double x1LeafPosition[40]; - double x2LeafPosition[40]; + // copy MLC data for proper (easier processing) + for ( vtkIdType leaf = 0; leaf < nofLeaves; leaf++) + { + double pos1 = table->GetValue( leaf, 0).ToDouble(); + double pos2 = table->GetValue( leaf, 1).ToDouble(); + mlcBoundary.push_back(std::make_pair( mlcBounds[leaf], mlcBounds[leaf + 1])); + mlcPosition.push_back(std::make_pair( pos1, pos2)); + } - // Calculate X1 first - for (int i = y1count; i >= y2count; i--) + MLCType::iterator firstLeafIterator = mlcBoundary.end(); + MLCType::iterator lastLeafIterator = mlcBoundary.end(); + double& jawBegin = this->Y1Jaw; + double& jawEnd = this->Y2Jaw; + if (!strncmp( "MLCX", mlcTableNode->GetName(), MLCX_STRING_SIZE)) // MLCX + { + jawBegin = this->Y1Jaw; + jawEnd = this->Y2Jaw; + } + else if (!strncmp( "MLCY", mlcTableNode->GetName(), MLCY_STRING_SIZE)) // MLCY { - double leafPosition = mlcArrayNode->GetArray()->GetComponent(i-20, 1); - if (leafPosition < this->X1Jaw) + jawBegin = this->X1Jaw; + jawEnd = this->X2Jaw; + } + // find first and last opened leaves + MLCType side1, side2; // temporary vectors to save visible points + for ( MLCType::iterator it = mlcBoundary.begin(); it != mlcBoundary.end(); ++it) + { + MLCType::iterator positer = it - mlcBoundary.begin() + mlcPosition.begin(); + bool mlcOpened = !vtkSlicerRtCommon::AreEqualWithTolerance( (*positer).first, (*positer).second); + if (mlcOpened && firstLeafIterator == mlcBoundary.end()) { - x1LeafPosition[i-20] = leafPosition; + firstLeafIterator = it; } - else + if (mlcOpened && firstLeafIterator != mlcBoundary.end()) { - x1LeafPosition[i-20] = this->X1Jaw; + lastLeafIterator = it; } } - // Calculate X2 next - for (int i = y1count; i >= y2count; i--) + + // iterate through visible leaves to fill temporary points vectors + if (firstLeafIterator != mlcBoundary.end() && lastLeafIterator != mlcBoundary.end()) { - double leafPosition = mlcArrayNode->GetArray()->GetComponent(i-20, 0); - if (leafPosition < this->X2Jaw) + MLCType::iterator firstLeafIterator1 = mlcBoundary.end(); + MLCType::iterator lastLeafIterator1 = mlcBoundary.end(); + // find first and last visible leaves using Jaws data + for ( MLCType::iterator it = firstLeafIterator; it <= lastLeafIterator; ++it) { - x2LeafPosition[i-20] = leafPosition; + if ((*it).first <= jawBegin && (*it).second > jawBegin) + { + firstLeafIterator1 = it; + } + if ((*it).first <= jawEnd && (*it).second > jawEnd) + { + lastLeafIterator1 = it; + } } - else + + // find opened MLC leaves into Jaws opening (logical AND) + if (firstLeafIterator1 != mlcBoundary.end() && lastLeafIterator1 != mlcBoundary.end()) { - x2LeafPosition[i-20] = this->X2Jaw; + lastLeafIterator = std::min( lastLeafIterator1, lastLeafIterator); + firstLeafIterator = std::max( firstLeafIterator1, firstLeafIterator); } - } - // Create beam model - points->InsertPoint(0,0,0,this->SAD); + // temporary vectors to save visible points + for ( MLCType::iterator iter = firstLeafIterator; iter <= lastLeafIterator; ++iter) + { + MLCType::iterator positer = iter - mlcBoundary.begin() + mlcPosition.begin(); + // add points for the visible leaves of side "1" and "2" + // into side1 and side2 points vectors + if (!strncmp( "MLCX", mlcTableNode->GetName(), MLCX_STRING_SIZE)) // MLCX + { + side1.push_back(std::make_pair( std::max( (*positer).first, this->X1Jaw), (*iter).first)); + side1.push_back(std::make_pair( std::max( (*positer).first, this->X1Jaw), (*iter).second)); + side2.push_back(std::make_pair( std::min( (*positer).second, this->X2Jaw), (*iter).first)); + side2.push_back(std::make_pair( std::min( (*positer).second, this->X2Jaw), (*iter).second)); + } + else if (!strncmp( "MLCY", mlcTableNode->GetName(), MLCY_STRING_SIZE)) // MLCY + { + side1.push_back(std::make_pair( (*iter).first, std::max( (*positer).first, this->Y1Jaw))); + side1.push_back(std::make_pair( (*iter).second, std::max( (*positer).first, this->Y1Jaw))); + side2.push_back(std::make_pair( (*iter).first, std::min( (*positer).second, this->Y2Jaw))); + side2.push_back(std::make_pair( (*iter).second, std::min( (*positer).second, this->Y2Jaw))); + } + } - int count = 1; - for (int i = y1count; i > y2count; i--) + // intersection between Jaws and MLC (logical AND) side "1" + for ( MLCType::iterator iter = side1.begin(); iter != side1.end(); ++iter) + { + if (!strncmp( "MLCX", mlcTableNode->GetName(), MLCX_STRING_SIZE)) // MLCX + { + // jaws X and mlcx + if ((*iter).second <= jawBegin) + { + (*iter).second = jawBegin; + } + else if ((*iter).second >= jawEnd) + { + (*iter).second = jawEnd; + } + } + else if (!strncmp( "MLCY", mlcTableNode->GetName(), MLCY_STRING_SIZE)) // MLCY + { + // jaws Y and mlcy + if ((*iter).first <= jawBegin) + { + (*iter).first = jawBegin; + } + else if ((*iter).first >= jawEnd) + { + (*iter).first = jawEnd; + } + } + } + // intersection between Jaws and MLC (logical AND) side "2" + for ( MLCType::iterator iter = side2.begin(); iter != side2.end(); ++iter) + { + if (!strncmp( "MLCX", mlcTableNode->GetName(), MLCX_STRING_SIZE)) // MLCX + { + // jaws X and mlcx + if ((*iter).second <= jawBegin) + { + (*iter).second = jawBegin; + } + else if ((*iter).second >= jawEnd) + { + (*iter).second = jawEnd; + } + } + else if (!strncmp( "MLCY", mlcTableNode->GetName(), MLCY_STRING_SIZE)) // MLCY + { + // jaws Y and mlcy + if ((*iter).first <= jawBegin) + { + (*iter).first = jawBegin; + } + else if ((*iter).first >= jawEnd) + { + (*iter).first = jawEnd; + } + } + } + + // reverse side "2" + std::reverse( side2.begin(), side2.end()); + + // fill real points vector s1 without excessive points from side1 vector + MLCType::value_type& p = *side1.begin(); // current (start) point + double& px = p.first; // x coordinate of current point + double& py = p.second; // y coordinate of current point + s1.push_back(p); + for ( size_t i = 1; i < side1.size() - 1; ++i) + { + double& pxNext = side1[i + 1].first; // x coordinate of next point + double& pyNext = side1[i + 1].second; // y coordinate of next point + if (!vtkSlicerRtCommon::AreEqualWithTolerance( px, pxNext) && + !vtkSlicerRtCommon::AreEqualWithTolerance( py, pyNext)) + { + p = side1[i]; + s1.push_back(p); + } + } + p = *(side1.end() - 1); + s1.push_back(p); + + // fill real points vector s2 without excessive points from side2 vector + p = *side2.begin(); + s2.push_back(p); + for ( size_t i = 1; i < side2.size() - 1; ++i) + { + double& pxNext = side2[i + 1].first; // x coordinate of next point + double& pyNext = side2[i + 1].second; // y coordinate of next point + if (!vtkSlicerRtCommon::AreEqualWithTolerance( px, pxNext) && + !vtkSlicerRtCommon::AreEqualWithTolerance( py, pyNext)) + { + p = side2[i]; + s2.push_back(p); + } + } + p = *(side2.end() - 1); + s2.push_back(p); + } + + // fill vtk points + points->InsertPoint( 0, 0, 0, this->SAD); // source + // side "1" using s1 points vector + for ( size_t i = 0; i < s1.size(); ++i) { - points->InsertPoint(count,-x1LeafPosition[i-20]*2, i*10*2, -this->SAD ); - count ++; - points->InsertPoint(count,-x1LeafPosition[i-20]*2, (i-1)*10*2, -this->SAD ); - count ++; + double& x = s1[i].first; + double& y = s1[i].second; + points->InsertPoint( i + 1, 2. * x, 2. * y, -this->SAD); } - - for (int i = y2count; i < y1count; i++) + // side "2" using s2 points vector + for ( size_t i = 0; i < s2.size(); ++i) { - points->InsertPoint(count,x2LeafPosition[i-20]*2, i*10*2, -this->SAD ); - count ++; - points->InsertPoint(count,x2LeafPosition[i-20]*2, (i+1)*10*2, -this->SAD ); - count ++; + double& x = s2[i].first; + double& y = s2[i].second; + points->InsertPoint( i + 1 + s1.size(), 2. * x, 2. * y, -this->SAD); } - for (int i = 1; i InsertNextCell(3); cellArray->InsertCellPoint(0); cellArray->InsertCellPoint(i); - cellArray->InsertCellPoint(i+1); + cellArray->InsertCellPoint(i + 1); } - // Add connection between X1 and X2 + // fill cell connection between side "1" -> side "2" cellArray->InsertNextCell(3); cellArray->InsertCellPoint(0); - cellArray->InsertCellPoint(numPointsEachSide); - cellArray->InsertCellPoint(numPointsEachSide+1); - for (int i = numPointsEachSide+1; i <2*numPointsEachSide; i++) + cellArray->InsertCellPoint(s1.size()); + cellArray->InsertCellPoint(s1.size() + 1); + + // fill cell array for side "2" + for ( size_t i = 1; i < s2.size(); ++i) { cellArray->InsertNextCell(3); cellArray->InsertCellPoint(0); - cellArray->InsertCellPoint(i); - cellArray->InsertCellPoint(i+1); + cellArray->InsertCellPoint(s1.size() + i); + cellArray->InsertCellPoint(s1.size() + i + 1); } - // Add connection between X1 and X2 + // fill cell connection between side "2" -> side "1" cellArray->InsertNextCell(3); cellArray->InsertCellPoint(0); - cellArray->InsertCellPoint(2*numPointsEachSide); + cellArray->InsertCellPoint(s1.size() + s2.size()); cellArray->InsertCellPoint(1); // Add the cap to the bottom - cellArray->InsertNextCell(2*numPointsEachSide); - for (int i = 1; i <= 2*numPointsEachSide; i++) + cellArray->InsertNextCell(s1.size() + s2.size()); + for ( size_t i = 1; i <= s1.size() + s2.size(); i++) { cellArray->InsertCellPoint(i); } diff --git a/Beams/MRML/vtkMRMLRTBeamNode.h b/Beams/MRML/vtkMRMLRTBeamNode.h index c98b0da0a..be9d92b9b 100644 --- a/Beams/MRML/vtkMRMLRTBeamNode.h +++ b/Beams/MRML/vtkMRMLRTBeamNode.h @@ -31,6 +31,7 @@ class vtkPolyData; class vtkMRMLScene; class vtkMRMLDoubleArrayNode; +class vtkMRMLTableNode; class vtkMRMLRTPlanNode; class vtkMRMLScalarVolumeNode; class vtkMRMLSegmentationNode; @@ -98,11 +99,17 @@ class VTK_SLICER_BEAMS_MODULE_MRML_EXPORT vtkMRMLRTBeamNode : public vtkMRMLMode /// Get parent plan node vtkMRMLRTPlanNode* GetParentPlanNode(); - /// Get MLC position double array node - vtkMRMLDoubleArrayNode* GetMLCPositionDoubleArrayNode(); - /// Set and observe MLC position double array node. + /// Get MLC boundary double array node + vtkMRMLDoubleArrayNode* GetMLCBoundaryDoubleArrayNode(); + /// Set and observe MLC boundary double array node. /// Triggers \sa BeamGeometryModified event and re-generation of beam model - void SetAndObserveMLCPositionDoubleArrayNode(vtkMRMLDoubleArrayNode* node); + void SetAndObserveMLCBoundaryDoubleArrayNode(vtkMRMLDoubleArrayNode* node); + + /// Get MLC position table node + vtkMRMLTableNode* GetMLCPositionTableNode(); + /// Set and observe MLC position table node + /// Triggers \sa BeamGeometryModified event and re-generation of beam model + void SetAndObserveMLCPositionTableNode(vtkMRMLTableNode* node); /// Get DRR volume node vtkMRMLScalarVolumeNode* GetDRRVolumeNode(); diff --git a/DicomRtImportExport/Logic/vtkSlicerDicomRtImportExportModuleLogic.cxx b/DicomRtImportExport/Logic/vtkSlicerDicomRtImportExportModuleLogic.cxx index 087d8512a..ea122c013 100644 --- a/DicomRtImportExport/Logic/vtkSlicerDicomRtImportExportModuleLogic.cxx +++ b/DicomRtImportExport/Logic/vtkSlicerDicomRtImportExportModuleLogic.cxx @@ -91,6 +91,8 @@ #include #include #include +#include +#include // Markups includes #include @@ -109,6 +111,8 @@ #include #include #include +#include +#include // ITK includes #include @@ -170,6 +174,14 @@ class vtkSlicerDicomRtImportExportModuleLogic::vtkInternal /// Add an ROI point to the scene vtkMRMLMarkupsFiducialNode* AddRoiPoint(double* roiPosition, std::string baseName, double* roiColor); + /// Create table node positions for the MLC + vtkMRMLTableNode* CreateMultiLeafCollimatorTableNode( const char* name, + const std::vector& mlcPositions); + + /// Create double array node boundaries for the MLC + vtkMRMLDoubleArrayNode* CreateMultiLeafCollimatorDoubleArrayNode( const char* name, + const std::vector& mlcBoundaries); + /// Compute and set geometry of an RT image /// \param node Either the volume node of the loaded RT image, or the isocenter fiducial node (corresponding to an RT image). This function is called both when /// loading an RT image and when loading a beam. Sets up the RT image geometry only if both information (the image itself and the isocenter data) are available @@ -800,8 +812,34 @@ bool vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::LoadExternalBeamPlan( } } + // Create MLC table node if MLCX or MLCY are available + std::vector boundaries, positions; + vtkMRMLTableNode* tableNode = nullptr; + vtkMRMLDoubleArrayNode* arrayNode = nullptr; + // Check MLC + const char* mlcName = rtReader->GetBeamMultiLeafCollimatorPositions( dicomBeamNumber, + boundaries, positions); + if (mlcName) + { + std::string mlcPositionString = std::string(mlcName) + "_Position"; + tableNode = this->CreateMultiLeafCollimatorTableNode( + mlcPositionString.c_str(), positions); + std::string mlcBoundaryString = std::string(mlcName) + "_Boundary"; + arrayNode = this->CreateMultiLeafCollimatorDoubleArrayNode( + mlcBoundaryString.c_str(), boundaries); + } + else + { + vtkDebugWithObjectMacro(this->External, "MLC data unavailable"); + } + // Add beam to scene (triggers poly data and transform creation and update) scene->AddNode(beamNode); + // Add MLC to beam + if (tableNode && arrayNode) { + beamNode->SetAndObserveMLCBoundaryDoubleArrayNode(arrayNode); + beamNode->SetAndObserveMLCPositionTableNode(tableNode); + } // Add beam to plan planNode->AddBeam(beamNode); // Update beam transforms (batch processing prevents processing events that would do this) @@ -1246,6 +1284,76 @@ vtkMRMLMarkupsFiducialNode* vtkSlicerDicomRtImportExportModuleLogic::vtkInternal return markupsNode; } +//--------------------------------------------------------------------------- +vtkMRMLDoubleArrayNode* vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::CreateMultiLeafCollimatorDoubleArrayNode( + const char* name, const std::vector& mlcBoundaries) +{ + vtkSmartPointer arrayNode = vtkSmartPointer::New(); + this->External->GetMRMLScene()->AddNode(arrayNode); + arrayNode->SetName(name); + + // Leaf boundaries + if (mlcBoundaries.size()) + { + vtkNew b; + b->SetNumberOfComponents(1); + b->SetNumberOfTuples(mlcBoundaries.size()); + for ( size_t i = 0; i < mlcBoundaries.size(); ++i) + { + b->InsertTuple( i, mlcBoundaries.data() + i); + } + arrayNode->SetArray(b); + return arrayNode; + } + else + { + vtkErrorWithObjectMacro( this->External, + "CreateMultiLeafCollimatorDoubleArrayNode: MLC boundaries array is empty"); + } + return nullptr; +} + +//--------------------------------------------------------------------------- +vtkMRMLTableNode* vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::CreateMultiLeafCollimatorTableNode( + const char* name, const std::vector& mlcPositions) +{ + vtkSmartPointer tableNode = vtkSmartPointer::New(); + this->External->GetMRMLScene()->AddNode(tableNode); + tableNode->SetName(name); + + vtkTable* table = tableNode->GetTable(); + if (table) + { + // Leaf position on the side "1" + vtkNew pos1; + pos1->SetName("1"); + table->AddColumn(pos1); + + // Leaf position on the side "2" + vtkNew pos2; + pos2->SetName("2"); + table->AddColumn(pos2); + + vtkIdType size = mlcPositions.size() / 2; + table->SetNumberOfRows(size); + for ( vtkIdType row = 0; row < size; ++row) + { + table->SetValue( row, 0, mlcPositions[row]); + table->SetValue( row, 1, mlcPositions[row + size]); + } + tableNode->SetUseColumnNameAsColumnHeader(true); + tableNode->SetColumnDescription( "1", "Leaf position on the side \"1\""); + tableNode->SetColumnDescription( "2", "Leaf position on the side \"2\""); + return tableNode; + } + else + { + vtkErrorWithObjectMacro( this->External, + "CreateMultiLeafCollimatorTableNode: unable to create vtkTable to fill MLC positions"); + } + return nullptr; +} + //------------------------------------------------------------------------------ void vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::SetupRtImageGeometry(vtkMRMLNode* node) { diff --git a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx index f9a94af16..fe99c0d69 100644 --- a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx +++ b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.cxx @@ -56,6 +56,13 @@ // Qt includes #include +namespace { + +const char* const MLCX_STRING = "MLCX"; +const char* const MLCY_STRING = "MLCY"; + +} // namespace + vtkStandardNewMacro(vtkSlicerDicomRtReader); //---------------------------------------------------------------------------- @@ -66,6 +73,29 @@ class vtkSlicerDicomRtReader::vtkInternal ~vtkInternal(); public: + /// Structure storing a Beam Limiting Device Parameters (MLC) + /// BeamLimitingDeviceEntry is a description of MLC, + /// or any beam limiting device such as symmetric or asymmetric jaws. + + /// DICOM standard describes two kinds of multi-leaf collimators: + /// "MLCX" - leaves moves along X-axis, "MCLY" - leaves moves along Y-axis. + class BeamLimitingDeviceEntry + { + public: + BeamLimitingDeviceEntry() + : + Distance(-1.), + NumberOfLeafJawPairs(0) + { + } + /// Source to BeamLimitingDevice (MLC) distance for RTPlan + /// or Isocenter to BeamLimitingDevice (MLC) distance for RTIonPlan + double Distance; // loaded, but not used + unsigned int NumberOfLeafJawPairs; + /// MLC position boundaries: Raw DICOM values + std::vector LeafPositionBoundary; + }; + /// Structure storing a ROI of an RT structure set class RoiEntry { @@ -109,6 +139,8 @@ class vtkSlicerDicomRtReader::vtkInternal LeafJawPositions[0][1] = 0.0; LeafJawPositions[1][0] = 0.0; LeafJawPositions[1][1] = 0.0; + SourceToJawDistance[0] = -1.; + SourceToJawDistance[1] = -1.; } unsigned int Number; std::string Name; @@ -128,6 +160,24 @@ class vtkSlicerDicomRtReader::vtkInternal double BeamLimitingDeviceAngle; /// Jaw positions: X and Y positions with isocenter as origin (e.g. {{-50,50}{-50,50}} ) double LeafJawPositions[2][2]; + + /// Jaw distance: X and Y distance from souce with isocenter as origin + /// SourceToJawDistance[0] - X or ASYMX + /// SourceToJawDistance[1] - Y or ASYMY + double SourceToJawDistance[2]; // loaded, but not used + + /// MLC parameters: Raw DICOM values + /// MultiLeafCollimator[0] - MLCX parameters + /// MultiLeafCollimator[1] - MLCY parameters + BeamLimitingDeviceEntry MultiLeafCollimator[2]; + + /// MLC positions: Raw DICOM values + /// LeafJawPositionsMLC[0] - leaf position vector for MLCX + /// LeafJawPositionsMLC[1] - leaf position vector for MLCY + // TODO: Positions should be vector for each control point + // not for only one control point. LoadRTPlan must load all + // control points not only one. + std::vector LeafJawPositionsMLC[2]; }; /// List of loaded beams from external beam plan @@ -558,6 +608,71 @@ void vtkSlicerDicomRtReader::vtkInternal::LoadRTPlan(DcmDataset* dataset) currentBeamSequenceItem.getSourceAxisDistance(sourceAxisDistance); beamEntry.SourceAxisDistance = sourceAxisDistance; + // Get multi leaf collimators parameters: distance, number of leaf pairs, leaf position boundaries + DRTBeamLimitingDeviceSequenceInRTBeamsModule& rtBeamLimitingDeviceSequence = + currentBeamSequenceItem.getBeamLimitingDeviceSequence(); + if (rtBeamLimitingDeviceSequence.gotoFirstItem().good()) + { + do + { + DRTBeamLimitingDeviceSequenceInRTBeamsModule::Item &collimatorItem = + rtBeamLimitingDeviceSequence.getCurrentItem(); + if (collimatorItem.isValid()) + { + OFString deviceType(""); + Sint32 nofPairs = -1; + + // source to beam limiting device (any collimator) distance + Float64 distance = -1.; + collimatorItem.getSourceToBeamLimitingDeviceDistance(distance); + + collimatorItem.getRTBeamLimitingDeviceType(deviceType); + if (!deviceType.compare("X") || !deviceType.compare("ASYMX")) + { + beamEntry.SourceToJawDistance[0] = distance; + } + else if (!deviceType.compare("Y") || !deviceType.compare("ASYMY")) + { + beamEntry.SourceToJawDistance[1] = distance; + } + else if (!deviceType.compare("MLCX") || !deviceType.compare("MLCY")) + { + OFCondition getNumberOfPairsCondition = collimatorItem.getNumberOfLeafJawPairs(nofPairs); + if (getNumberOfPairsCondition.good()) + { + OFVector leafPositionBoundaries; + OFCondition getBoundariesCondition = collimatorItem.getLeafPositionBoundaries(leafPositionBoundaries); + if (getBoundariesCondition.good()) + { + double& distanceX = beamEntry.MultiLeafCollimator[0].Distance; + double& distanceY = beamEntry.MultiLeafCollimator[1].Distance; + unsigned int& pairsX = beamEntry.MultiLeafCollimator[0].NumberOfLeafJawPairs; + unsigned int& pairsY = beamEntry.MultiLeafCollimator[1].NumberOfLeafJawPairs; + std::vector& boundX = beamEntry.MultiLeafCollimator[0].LeafPositionBoundary; + std::vector& boundY = beamEntry.MultiLeafCollimator[1].LeafPositionBoundary; + if (!deviceType.compare("MLCX")) + { + distanceX = distance; + pairsX = nofPairs; + boundX.resize(leafPositionBoundaries.size()); + std::copy( leafPositionBoundaries.begin(), leafPositionBoundaries.end(), boundX.begin()); + vtkDebugWithObjectMacro( this->External, "LoadRTPlan: MLCX number of leaf/jaw pairs: " << nofPairs); + } + else if (!deviceType.compare("MLCY")) + { + distanceY = distance; + pairsY = nofPairs; + boundY.resize(leafPositionBoundaries.size()); + std::copy( leafPositionBoundaries.begin(), leafPositionBoundaries.end(), boundY.begin()); + vtkDebugWithObjectMacro( this->External, "LoadRTPlan: MLCY number of leaf/jaw pairs: " << nofPairs); + } + } + } + } + } + } while (rtBeamLimitingDeviceSequence.gotoNextItem().good()); + } + DRTControlPointSequence &rtControlPointSequence = currentBeamSequenceItem.getControlPointSequence(); if (!rtControlPointSequence.gotoFirstItem().good()) { @@ -636,7 +751,24 @@ void vtkSlicerDicomRtReader::vtkInternal::LoadRTPlan(DcmDataset* dataset) } else if ( !rtBeamLimitingDeviceType.compare("MLCX") || !rtBeamLimitingDeviceType.compare("MLCY") ) { - vtkWarningWithObjectMacro(this->External, "LoadRTPlan: Multi-leaf collimator entry found. This collimator type is not yet supported"); + // Get MLC leaves positions + if (getJawPositionsCondition.good()) + { + std::vector& positionsX = beamEntry.LeafJawPositionsMLC[0]; + std::vector& positionsY = beamEntry.LeafJawPositionsMLC[1]; + if (!rtBeamLimitingDeviceType.compare("MLCX")) + { + positionsX.resize(leafJawPositions.size()); + std::copy( leafJawPositions.begin(), leafJawPositions.end(), positionsX.begin()); + vtkDebugWithObjectMacro( this->External, "LoadRTPlan: MLCX leaf positions have been loaded"); + } + else if (!rtBeamLimitingDeviceType.compare("MLCY")) + { + positionsY.resize(leafJawPositions.size()); + std::copy( leafJawPositions.begin(), leafJawPositions.end(), positionsY.begin()); + vtkDebugWithObjectMacro( this->External, "LoadRTPlan: MLCY leaf positions have been loaded"); + } + } } else { @@ -1614,6 +1746,88 @@ void vtkSlicerDicomRtReader::GetBeamLeafJawPositions(unsigned int beamNumber, do jawPositions[1][1]=beam->LeafJawPositions[1][1]; } +//---------------------------------------------------------------------------- +bool vtkSlicerDicomRtReader::GetBeamMultiLeafCollimatorPositionsX( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions) +{ + vtkInternal::BeamEntry* beam = this->Internal->FindBeamByNumber(beamNumber); + if (beam) { + const unsigned int& pairs = beam->MultiLeafCollimator[0].NumberOfLeafJawPairs; + size_t positions = beam->LeafJawPositionsMLC[0].size(); + size_t boundaries = beam->MultiLeafCollimator[0].LeafPositionBoundary.size(); + if (pairs && positions && boundaries && + ((pairs * 2) == positions) && ((pairs + 1) == boundaries)) + { + pairBoundaries = beam->MultiLeafCollimator[0].LeafPositionBoundary; + leafPositions = beam->LeafJawPositionsMLC[0]; + return true; + } + else + { + vtkWarningMacro("GetBeamMultiLeafCollimatorPositionsX: " \ + "MLCX parameters undefined, " \ + "or different number of leaf pairs and positions"); + } + } + else + { + vtkWarningMacro("GetBeamMultiLeafCollimatorPositionsX: Unable to find beam of number" << beamNumber); + } + return false; +} + +//---------------------------------------------------------------------------- +bool vtkSlicerDicomRtReader::GetBeamMultiLeafCollimatorPositionsY( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions) +{ + vtkInternal::BeamEntry* beam = this->Internal->FindBeamByNumber(beamNumber); + if (beam) { + const unsigned int& pairs = beam->MultiLeafCollimator[1].NumberOfLeafJawPairs; + size_t positions = beam->LeafJawPositionsMLC[1].size(); + size_t boundaries = beam->MultiLeafCollimator[1].LeafPositionBoundary.size(); + if (pairs && positions && boundaries && + ((pairs * 2) == positions) && ((pairs + 1) == boundaries)) + { + pairBoundaries = beam->MultiLeafCollimator[1].LeafPositionBoundary; + leafPositions = beam->LeafJawPositionsMLC[1]; + return true; + } + else + { + vtkWarningMacro("GetBeamMultiLeafCollimatorPositionsY: " \ + "MLCY parameters undefined, " \ + "or different number of leaf pairs and positions"); + } + } + else + { + vtkWarningMacro("GetBeamMultiLeafCollimatorPositionsY: Unable to find beam of number" << beamNumber); + } + return false; +} + +//---------------------------------------------------------------------------- +const char* vtkSlicerDicomRtReader::GetBeamMultiLeafCollimatorPositions( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions) +{ + bool mlcxIsValid = this->GetBeamMultiLeafCollimatorPositionsX( beamNumber, + pairBoundaries, leafPositions); + if (mlcxIsValid) + { + return MLCX_STRING; + } + else + { + bool mlcyIsValid = this->GetBeamMultiLeafCollimatorPositionsY( beamNumber, + pairBoundaries, leafPositions); + if (mlcyIsValid) + { + return MLCY_STRING; + } + } + return nullptr; +} + //---------------------------------------------------------------------------- int vtkSlicerDicomRtReader::GetNumberOfChannels() { diff --git a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h index 0583f84fe..528d9ef1c 100644 --- a/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h +++ b/DicomRtImportExport/Logic/vtkSlicerDicomRtReader.h @@ -35,6 +35,9 @@ // SlicerRtCommon includes #include "vtkSlicerDicomReaderBase.h" +// STD includes +#include + class vtkPolyData; /// \ingroup SlicerRt_QtModules_DicomRtImport @@ -72,7 +75,6 @@ class VTK_SLICER_DICOMRTIMPORTEXPORT_LOGIC_EXPORT vtkSlicerDicomRtReader : publi /// \param internalIndex Internal index of ROI to get int GetRoiNumber(unsigned int internalIndex); - /// Get number of beams int GetNumberOfBeams(); @@ -101,6 +103,26 @@ class VTK_SLICER_DICOMRTIMPORTEXPORT_LOGIC_EXPORT vtkSlicerDicomRtReader : publi /// \param jawPositions Array in which the jaw positions are copied void GetBeamLeafJawPositions(unsigned int beamNumber, double jawPositions[2][2]); + /// Get MLCX leaves boundaries & leaves positions opening for a given beam + /// \param pairBoundaries Array in which the raw leaves boundaries are copied + /// \param leafPositions Array in which the raw leaf positions are copied + /// \return true if data is valid, false otherwise + bool GetBeamMultiLeafCollimatorPositionsX( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions); + + /// Get MLCY leaves boundaries & leaves positions opening for a given beam + /// \param pairBoundaries Array in which the raw leaves boundaries are copied + /// \param leafPositions Array in which the raw leaf positions are copied + /// \return true if data is valid, false otherwise + bool GetBeamMultiLeafCollimatorPositionsY( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions); + + /// Get MLC leaves boundaries & leaves positions opening for a given beam + /// \param pairBoundaries Array in which the raw leaves boundaries are copied + /// \param leafPositions Array in which the raw leaf positions are copied + /// \return "MLCX" or "MLCY" if data is valid, nullptr otherwise + const char* GetBeamMultiLeafCollimatorPositions( unsigned int beamNumber, + std::vector& pairBoundaries, std::vector& leafPositions); /// Get number of channels int GetNumberOfChannels();