Skip to content

Commit

Permalink
BUG: Always consider both isocenter and patient support transforms
Browse files Browse the repository at this point in the history
See #218 (comment)

Still to fix: when beam is selected in REV module the patient support related transforms do not consider the beam.

Re #218
  • Loading branch information
cpinter committed Oct 5, 2023
1 parent 3ae787b commit 1782b7a
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 69 deletions.
4 changes: 2 additions & 2 deletions Beams/Logic/vtkSlicerBeamsModuleLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ void vtkSlicerBeamsModuleLogic::UpdateTransformForBeam(vtkMRMLRTBeamNode* beamNo
}

//---------------------------------------------------------------------------
void vtkSlicerBeamsModuleLogic::UpdateTransformForBeam( vtkMRMLScene* beamSequenceScene,
void vtkSlicerBeamsModuleLogic::UpdateTransformForBeam(vtkMRMLScene* beamSequenceScene,
vtkMRMLRTBeamNode* beamNode, vtkMRMLLinearTransformNode* beamTransformNode, double* isocenter)
{
if (!beamNode)
Expand All @@ -211,7 +211,7 @@ void vtkSlicerBeamsModuleLogic::UpdateTransformForBeam( vtkMRMLScene* beamSequen
//TODO: Use one IEC logic in a private scene for all beam transform updates?
vtkSmartPointer<vtkSlicerIECTransformLogic> iecLogic = vtkSmartPointer<vtkSlicerIECTransformLogic>::New();
iecLogic->SetMRMLScene(beamSequenceScene);
iecLogic->UpdateBeamTransform( beamNode, beamTransformNode, isocenter);
iecLogic->UpdateBeamTransform(beamNode, beamTransformNode, isocenter);
}

//----------------------------------------------------------------------------
Expand Down
70 changes: 49 additions & 21 deletions Beams/Logic/vtkSlicerIECTransformLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ void vtkSlicerIECTransformLogic::UpdateIECTransformsFromBeam(vtkMRMLRTBeamNode*
vtkTransform* rasToPatientReferenceTransform = vtkTransform::SafeDownCast(rasToPatientReferenceTransformNode->GetTransformToParent());
rasToPatientReferenceTransform->Identity();
// Apply isocenter translation
std::array< double, 3 > isocenterPosition = { 0.0, 0.0, 0.0 };
std::array<double, 3> isocenterPosition = {0.0, 0.0, 0.0};
if (isocenter)
{
// This is dirty hack for dynamic beams, the actual translation
Expand All @@ -342,41 +342,69 @@ void vtkSlicerIECTransformLogic::UpdateIECTransformsFromBeam(vtkMRMLRTBeamNode*
}
}

rasToPatientReferenceTransform->RotateX(-90.);
rasToPatientReferenceTransform->RotateZ(180.);
rasToPatientReferenceTransform->RotateX(-90.0);
rasToPatientReferenceTransform->RotateZ(180.0);
rasToPatientReferenceTransform->Modified();

// Update IEC FixedReference to RAS transform based on the isocenter defined in the beam's parent plan
vtkMRMLLinearTransformNode* fixedReferenceToRasTransformNode =
this->GetTransformNodeBetween(FixedReference, RAS);
vtkTransform* fixedReferenceToRasTransform = vtkTransform::SafeDownCast(fixedReferenceToRasTransformNode->GetTransformToParent());
fixedReferenceToRasTransform->Identity();
// Update fixed reference to RAS transform as well
vtkMRMLRTPlanNode* parentPlanNode = beamNode->GetParentPlanNode();
this->UpdateFixedReferenceToRASTransform(parentPlanNode, isocenter);
}

// Apply isocenter translation
if (isocenter)
//-----------------------------------------------------------------------------
void vtkSlicerIECTransformLogic::UpdateFixedReferenceToRASTransform(vtkMRMLRTPlanNode* planNode, double* isocenter)
{
if (!this->GetMRMLScene())
{
// Once again the dirty hack for dynamic beams, the actual translation
// will be in vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::LoadDynamicBeamSequence method
rasToPatientReferenceTransform->Translate(isocenterPosition[0], isocenterPosition[1], isocenterPosition[2]);
vtkErrorMacro("UpdateFixedReferenceToRasTransform: Invalid MRML scene");
return;
}
else

// Update IEC FixedReference to RAS transform based on the isocenter defined in the beam's parent plan
vtkMRMLLinearTransformNode* fixedReferenceToRasTransformNode = this->GetTransformNodeBetween(FixedReference, RAS);

// Apply isocenter translation
vtkNew<vtkTransform> fixedReferenceToRASTransformBeamComponent;
if (planNode)
{
// translation for a static beam
if (beamNode->GetPlanIsocenterPosition(isocenterPosition.data()))
std::array<double, 3> isocenterPosition = {0.0, 0.0, 0.0};
if (isocenter)
{
fixedReferenceToRasTransform->Translate(isocenterPosition[0], isocenterPosition[1], isocenterPosition[2]);
// Once again the dirty hack for dynamic beams, the actual translation
// will be in vtkSlicerDicomRtImportExportModuleLogic::vtkInternal::LoadDynamicBeamSequence method
fixedReferenceToRASTransformBeamComponent->Translate(isocenterPosition[0], isocenterPosition[1], isocenterPosition[2]);
}
else
{
vtkErrorMacro("UpdateIECTransformsFromBeam: Failed to get isocenter position for beam " << beamNode->GetName());
// translation for a static beam
if (planNode->GetIsocenterPosition(isocenterPosition.data()))
{
fixedReferenceToRASTransformBeamComponent->Translate(isocenterPosition[0], isocenterPosition[1], isocenterPosition[2]);
}
else
{
vtkErrorMacro("UpdateFixedReferenceToRasTransform: Failed to get isocenter position for plan " << planNode->GetName());
}
}
}

// The "S" direction in RAS is the "A" direction in FixedReference
fixedReferenceToRasTransform->RotateX(-90.0);
fixedReferenceToRASTransformBeamComponent->RotateX(-90.0);
// The "S" direction to be toward the gantry (head first position) by default
fixedReferenceToRasTransform->RotateZ(180.0);
fixedReferenceToRasTransform->Modified();
fixedReferenceToRASTransformBeamComponent->RotateZ(180.0);
fixedReferenceToRASTransformBeamComponent->Modified();

vtkMRMLLinearTransformNode* patientSupportRotationToFixedReferenceTransformNode =
this->GetTransformNodeBetween(PatientSupportRotation, FixedReference);
vtkMRMLLinearTransformNode* tableTopToTableTopEccentricRotationTransformNode =
this->GetTransformNodeBetween(TableTop, TableTopEccentricRotation);

vtkNew<vtkTransform> fixedReferenceToRASTransform;
fixedReferenceToRASTransform->Concatenate(fixedReferenceToRASTransformBeamComponent);
fixedReferenceToRASTransform->Concatenate(vtkTransform::SafeDownCast(tableTopToTableTopEccentricRotationTransformNode->GetTransformFromParent()));
fixedReferenceToRASTransform->Concatenate(vtkTransform::SafeDownCast(patientSupportRotationToFixedReferenceTransformNode->GetTransformFromParent()));

fixedReferenceToRasTransformNode->SetAndObserveTransformToParent(fixedReferenceToRASTransform);
}

//-----------------------------------------------------------------------------
Expand Down
10 changes: 7 additions & 3 deletions Beams/Logic/vtkSlicerIECTransformLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

class vtkGeneralTransform;
class vtkMRMLRTBeamNode;
class vtkMRMLRTPlanNode;
class vtkMRMLLinearTransformNode;

/// \ingroup SlicerRt_QtModules_Beams
Expand All @@ -45,7 +46,7 @@ class vtkMRMLLinearTransformNode;
/// system to another by simply inputting the coordinate systems. The logic can observe an
/// RT beam node to get the geometrical parameters defining the state of the objects involved.
/// Image describing these coordinate frames:
/// http://perk.cs.queensu.ca/sites/perkd7.cs.queensu.ca/files/Project/IEC_Transformations.PNG
/// https://github.com/SlicerRt/SlicerRtDoc/blob/master/technical/IEC%2061217-2002_CoordinateSystemsDiagram_HiRes.png
///

/*
Expand Down Expand Up @@ -143,10 +144,13 @@ class VTK_SLICER_BEAMS_LOGIC_EXPORT vtkSlicerIECTransformLogic : public vtkMRMLA
void UpdateBeamTransform(vtkMRMLRTBeamNode* beamNode);
/// Update parent transform node of a given beam from the IEC transform hierarchy and the beam parameters
/// \warning This method is used only in vtkSlicerBeamsModuleLogic::UpdateTransformForBeam
void UpdateBeamTransform( vtkMRMLRTBeamNode* beamNode, vtkMRMLLinearTransformNode* beamTransformNode, double* isocenter);
void UpdateBeamTransform(vtkMRMLRTBeamNode* beamNode, vtkMRMLLinearTransformNode* beamTransformNode, double* isocenter);

/// Update IEC transforms according to beam node
void UpdateIECTransformsFromBeam( vtkMRMLRTBeamNode* beamNode, double* isocenter = nullptr);
void UpdateIECTransformsFromBeam(vtkMRMLRTBeamNode* beamNode, double* isocenter = nullptr);

/// Update fixed reference to RAS transform based on isocenter and patient support transforms
void UpdateFixedReferenceToRASTransform(vtkMRMLRTPlanNode* planNode, double* isocenter = nullptr);

protected:
/// Get name of transform node between two coordinate systems
Expand Down
24 changes: 0 additions & 24 deletions RoomsEyeView/Logic/vtkSlicerRoomsEyeViewModuleLogic.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -959,30 +959,6 @@ bool vtkSlicerRoomsEyeViewModuleLogic::GetPatientBodyPolyData(vtkMRMLRoomsEyeVie
patientBodyPolyData );
}

//----------------------------------------------------------------------------
void vtkSlicerRoomsEyeViewModuleLogic::UpdateFixedReferenceToRASTransform(vtkMRMLRoomsEyeViewNode* parameterNode)
{
if (!parameterNode)
{
vtkErrorMacro("UpdateFixedReferenceToRASTransform: Invalid parameter set node");
return;
}

vtkMRMLLinearTransformNode* fixedReferenceToRasTransformNode =
this->IECLogic->GetTransformNodeBetween(vtkSlicerIECTransformLogic::FixedReference, vtkSlicerIECTransformLogic::RAS);

vtkMRMLLinearTransformNode* patientSupportRotationToFixedReferenceTransformNode =
this->IECLogic->GetTransformNodeBetween(vtkSlicerIECTransformLogic::PatientSupportRotation, vtkSlicerIECTransformLogic::FixedReference);
vtkMRMLLinearTransformNode* tableTopToTableTopEccentricRotationTransformNode =
this->IECLogic->GetTransformNodeBetween(vtkSlicerIECTransformLogic::TableTop, vtkSlicerIECTransformLogic::TableTopEccentricRotation);

vtkNew<vtkTransform> fixedReferenceToRASTransform;
fixedReferenceToRASTransform->Concatenate(vtkTransform::SafeDownCast(tableTopToTableTopEccentricRotationTransformNode->GetTransformFromParent()));
fixedReferenceToRASTransform->Concatenate(vtkTransform::SafeDownCast(patientSupportRotationToFixedReferenceTransformNode->GetTransformFromParent()));

fixedReferenceToRasTransformNode->SetAndObserveTransformToParent(fixedReferenceToRASTransform);
}

//----------------------------------------------------------------------------
void vtkSlicerRoomsEyeViewModuleLogic::UpdateGantryToFixedReferenceTransform(vtkMRMLRoomsEyeViewNode* parameterNode)
{
Expand Down
3 changes: 0 additions & 3 deletions RoomsEyeView/Logic/vtkSlicerRoomsEyeViewModuleLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,6 @@ class VTK_SLICER_ROOMSEYEVIEW_LOGIC_EXPORT vtkSlicerRoomsEyeViewModuleLogic :
/// Create or get transforms taking part in the IEC logic and additional devices, and build the transform hierarchy
void BuildRoomsEyeViewTransformHierarchy();

/// Update FixedReferenceToRAS transform, making sure the patient stays fixed on the table top
void UpdateFixedReferenceToRASTransform(vtkMRMLRoomsEyeViewNode* parameterNode);

/// Update GantryToFixedReference transform based on gantry angle from UI slider
void UpdateGantryToFixedReferenceTransform(vtkMRMLRoomsEyeViewNode* parameterNode);

Expand Down
77 changes: 61 additions & 16 deletions RoomsEyeView/qSlicerRoomsEyeViewModuleWidget.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class qSlicerRoomsEyeViewModuleWidgetPrivate : public Ui_qSlicerRoomsEyeViewModu
~qSlicerRoomsEyeViewModuleWidgetPrivate() = default;
vtkSmartPointer<vtkSlicerRoomsEyeViewModuleLogic> logic() const;

vtkMRMLRTPlanNode* currentPlanNode(vtkMRMLRoomsEyeViewNode* paramNode);

bool ModuleWindowInitialized;
};

Expand All @@ -105,6 +107,49 @@ vtkSmartPointer<vtkSlicerRoomsEyeViewModuleLogic> qSlicerRoomsEyeViewModuleWidge
return vtkSlicerRoomsEyeViewModuleLogic::SafeDownCast(q->logic());
}

//-----------------------------------------------------------------------------
vtkMRMLRTPlanNode* qSlicerRoomsEyeViewModuleWidgetPrivate::currentPlanNode(vtkMRMLRoomsEyeViewNode* paramNode)
{
if (!paramNode)
{
qCritical() << Q_FUNC_INFO << ": Invalid parameter node given";
return nullptr;
}

vtkMRMLRTBeamNode* beamNode = paramNode->GetBeamNode();
if (beamNode)
{
return beamNode->GetParentPlanNode();
}

vtkMRMLSegmentationNode* segmentationNode = paramNode->GetPatientBodySegmentationNode();
if (segmentationNode)
{
// Try to get the plan from the same study
vtkMRMLScene* scene = segmentationNode->GetScene();
vtkMRMLSubjectHierarchyNode* shNode = scene->GetSubjectHierarchyNode();
vtkIdType segmentationItemID = shNode->GetItemByDataNode(segmentationNode);
vtkIdType studyItemID = shNode->GetItemAncestorAtLevel(segmentationItemID, vtkMRMLSubjectHierarchyConstants::GetDICOMLevelStudy());
if (studyItemID == vtkMRMLSubjectHierarchyNode::INVALID_ITEM_ID)
{
qCritical() << Q_FUNC_INFO << ": Failed to find current plan, because there is no beam in the parameter node, and the patient segmentation is not in a study";
return nullptr;
}
std::vector<vtkIdType> studyItemIDs;
shNode->GetItemChildren(shNode->GetSceneItemID(), studyItemIDs, true);
std::vector<vtkIdType>::iterator itemIt;
for (itemIt=studyItemIDs.begin(); itemIt!=studyItemIDs.end(); ++itemIt)
{
vtkMRMLRTPlanNode* planNode = vtkMRMLRTPlanNode::SafeDownCast(shNode->GetItemDataNode(*itemIt));
if (planNode)
{
return planNode;
}
}
}

return nullptr;
}

//-----------------------------------------------------------------------------
// qSlicerRoomsEyeViewModuleWidget methods
Expand Down Expand Up @@ -477,22 +522,22 @@ void qSlicerRoomsEyeViewModuleWidget::onLoadTreatmentMachineButtonClicked()
std::vector<vtkIdType> machineFolderItemIDs;
std::vector<vtkIdType>::iterator itemIt;
for (itemIt=allItemIDs.begin(); itemIt!=allItemIDs.end(); ++itemIt)
{
{
std::string machineDescriptorFilePath = shNode->GetItemAttribute(*itemIt,
vtkSlicerRoomsEyeViewModuleLogic::TREATMENT_MACHINE_DESCRIPTOR_FILE_PATH_ATTRIBUTE_NAME);
if (!machineDescriptorFilePath.compare(descriptorFilePath.toUtf8().constData()))
{
{
QMessageBox::warning(this, tr("Machine already loaded"), tr("This treatment machine is already loaded."));
return;
}
}
if (!machineDescriptorFilePath.empty())
{
{
machineFolderItemIDs.push_back(*itemIt);
}
}
}

if (machineFolderItemIDs.size() > 0)
{
{
ctkMessageBox* existingMachineMsgBox = new ctkMessageBox(this);
existingMachineMsgBox->setWindowTitle(tr("Other machines loaded"));
existingMachineMsgBox->setText(tr("There is another treatment machine loaded in the scene. Would you like to hide or delete it?"));
Expand All @@ -507,22 +552,22 @@ void qSlicerRoomsEyeViewModuleWidget::onLoadTreatmentMachineButtonClicked()
existingMachineMsgBox->exec();
int resultCode = existingMachineMsgBox->buttonRole(existingMachineMsgBox->clickedButton());
if (resultCode == QMessageBox::AcceptRole)
{
{
qSlicerSubjectHierarchyFolderPlugin* folderPlugin = qobject_cast<qSlicerSubjectHierarchyFolderPlugin*>(
qSlicerSubjectHierarchyPluginHandler::instance()->pluginByName("Folder") );
for (itemIt=machineFolderItemIDs.begin(); itemIt!=machineFolderItemIDs.end(); ++itemIt)
{
{
folderPlugin->setDisplayVisibility(*itemIt, false);
}
}
}
else if (resultCode == QMessageBox::DestructiveRole)
{
{
for (itemIt=machineFolderItemIDs.begin(); itemIt!=machineFolderItemIDs.end(); ++itemIt)
{
{
shNode->RemoveItem(*itemIt);
}
}
}
}

// Load and setup models
paramNode->SetTreatmentMachineDescriptorFilePath(descriptorFilePath.toUtf8().constData());
Expand Down Expand Up @@ -650,7 +695,7 @@ void qSlicerRoomsEyeViewModuleWidget::onPatientSupportRotationSliderValueChanged

// Update IEC transform
d->logic()->UpdatePatientSupportRotationToFixedReferenceTransform(paramNode);
d->logic()->UpdateFixedReferenceToRASTransform(paramNode);
d->logic()->GetIECLogic()->UpdateFixedReferenceToRASTransform(d->currentPlanNode(paramNode));

// Update beam parameter
vtkMRMLRTBeamNode* beamNode = vtkMRMLRTBeamNode::SafeDownCast(paramNode->GetBeamNode());
Expand Down Expand Up @@ -680,7 +725,7 @@ void qSlicerRoomsEyeViewModuleWidget::onVerticalTableTopDisplacementSliderValueC

d->logic()->UpdatePatientSupportToPatientSupportRotationTransform(paramNode);
d->logic()->UpdateTableTopToTableTopEccentricRotationTransform(paramNode);
d->logic()->UpdateFixedReferenceToRASTransform(paramNode);
d->logic()->GetIECLogic()->UpdateFixedReferenceToRASTransform(d->currentPlanNode(paramNode));

this->checkForCollisions();
this->updateTreatmentOrientationMarker();
Expand All @@ -702,7 +747,7 @@ void qSlicerRoomsEyeViewModuleWidget::onLongitudinalTableTopDisplacementSliderVa
paramNode->DisableModifiedEventOff();

d->logic()->UpdateTableTopToTableTopEccentricRotationTransform(paramNode);
d->logic()->UpdateFixedReferenceToRASTransform(paramNode);
d->logic()->GetIECLogic()->UpdateFixedReferenceToRASTransform(d->currentPlanNode(paramNode));

this->checkForCollisions();
this->updateTreatmentOrientationMarker();
Expand All @@ -726,7 +771,7 @@ void qSlicerRoomsEyeViewModuleWidget::onLateralTableTopDisplacementSliderValueCh
paramNode->DisableModifiedEventOff();

d->logic()->UpdateTableTopToTableTopEccentricRotationTransform(paramNode);
d->logic()->UpdateFixedReferenceToRASTransform(paramNode);
d->logic()->GetIECLogic()->UpdateFixedReferenceToRASTransform(d->currentPlanNode(paramNode));

this->checkForCollisions();
this->updateTreatmentOrientationMarker();
Expand Down

0 comments on commit 1782b7a

Please sign in to comment.