Skip to content

Commit d61abc4

Browse files
author
Mikhail Polkovnikov
committed
Merge remote-tracking branch 'upstream/master' into arc-beam
2 parents c560fab + 503f7bc commit d61abc4

File tree

14 files changed

+120
-56
lines changed

14 files changed

+120
-56
lines changed

BatchProcessing/BatchStructureSetConversion.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def TestSection_0_SetupPathsAndNames(self):
237237
if not os.access(self.dataDir, os.F_OK):
238238
os.mkdir(self.dataDir)
239239
self.dicomDatabaseDir = self.dataDir + '/CtkDicomDatabase'
240-
self.dicomZipFileUrl = 'http://slicer.kitware.com/midas3/download/folder/2822/TinyRtStudy.zip'
240+
self.dicomZipFileUrl = 'https://github.com/SlicerRt/SlicerRtData/releases/download/TestData/plastimatch_tiny-rt-study.zip'
241241
self.dicomZipFilePath = self.logic.dataDir + '/TinyRtStudy.zip'
242242
self.expectedNumOfFilesInDicomDataDir = 12
243243
self.db = None

Beams/MRML/vtkMRMLRTBeamNode.cxx

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ vtkMRMLRTBeamNode::vtkMRMLRTBeamNode()
7474
this->CollimatorAngle = 0.0;
7575
this->CouchAngle = 0.0;
7676

77-
this->SAD = 1000.0;
77+
this->SAD = 2000.0;
7878

7979
this->SourceToJawsDistanceX = 500.;
8080
this->SourceToJawsDistanceY = 500.;

Beams/Widgets/qMRMLBeamParametersTabWidget.cxx

+10-9
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,11 @@ void qMRMLBeamParametersTabWidgetPrivate::init()
122122
}
123123
else
124124
{
125-
qSlicerModuleManager* manager = app->moduleManager();
126-
vtkMRMLAbstractLogic* logic = manager->module("Beams")->logic();
127-
vtkSlicerBeamsModuleLogic* beamsLogic = vtkSlicerBeamsModuleLogic::SafeDownCast(logic);
128-
this->MLCPositionLogic = beamsLogic->GetMLCPositionLogic();
125+
vtkSlicerBeamsModuleLogic* beamsLogic = vtkSlicerBeamsModuleLogic::SafeDownCast(app->moduleLogic("Beams"));
126+
if (beamsLogic)
127+
{
128+
this->MLCPositionLogic = beamsLogic->GetMLCPositionLogic();
129+
}
129130
}
130131

131132
// Remove Visualization tab as it is not yet functional
@@ -768,7 +769,7 @@ void qMRMLBeamParametersTabWidget::mlcBoundaryAndPositionTableNodeChanged(vtkMRM
768769
// Get rt plan node for ExternalBeamPlanning node
769770
vtkMRMLRTPlanNode* rtPlanNode = d->logic()->GetExternalBeamPlanningNode()->GetRtPlanNode();
770771
if (!rtPlanNode)
771-
{
772+
{
772773
qCritical() << Q_FUNC_INFO << ": Invalid plan node!";
773774
return;
774775
}
@@ -806,8 +807,8 @@ void qMRMLBeamParametersTabWidget::generateMLCboundaryClicked()
806807
int nofPairs = static_cast<int>(d->SliderWidget_NumberOfLeafPairs->value());
807808
double leafPairSize = d->SliderWidget_LeafPairBoundarySize->value();
808809
double offset = d->SliderWidget_IsocenterOffset->value();
809-
810-
vtkMRMLTableNode* mlcTable = d->MLCPositionLogic->CreateMultiLeafCollimatorTableNodeBoundaryData(
810+
811+
vtkMRMLTableNode* mlcTable = d->MLCPositionLogic->CreateMultiLeafCollimatorTableNodeBoundaryData(
811812
mlcType, nofPairs, leafPairSize, offset);
812813
if (mlcTable)
813814
{
@@ -907,7 +908,7 @@ void qMRMLBeamParametersTabWidget::calculateMLCPositionClicked()
907908
vtkMRMLTransformNode* beamTransformNode = d->BeamNode->GetParentTransformNode();
908909

909910
vtkMRMLTableNode* mlcTableNode = vtkMRMLTableNode::SafeDownCast(mlcTable);
910-
if (mlcTableNode && d->MLCPositionLogic->CalculateMultiLeafCollimatorPosition( mlcTableNode, convexHullCurve)
911+
if (mlcTableNode && d->MLCPositionLogic->CalculateMultiLeafCollimatorPosition( mlcTableNode, convexHullCurve)
911912
&& d->MLCPositionLogic->CalculateMultiLeafCollimatorPosition( d->BeamNode, mlcTableNode, targetPoly))
912913
{
913914
d->BeamNode->SetAndObserveMultiLeafCollimatorTableNode(mlcTableNode);
@@ -1123,7 +1124,7 @@ void qMRMLBeamParametersTabWidget::contoursInBEWClicked(bool checked)
11231124
}
11241125

11251126
// TODO: add the logic to check if contours should be included in the DRR view
1126-
// right now the contours are included always.
1127+
// right now the contours are included always.
11271128
if (checked)
11281129
{
11291130
}

DicomRtImportExport/ConversionRules/vtkPlanarContourToClosedSurfaceConversionRule.cxx

+9-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "vtkPlanarContourToClosedSurfaceConversionRule.h"
2222

2323
// VTK includes
24+
#include <vtkCleanPolyData.h>
2425
#include <vtkExtractCells.h>
2526
#include <vtkImageAccumulate.h>
2627
#include <vtkImageData.h>
@@ -1355,6 +1356,12 @@ void vtkPlanarContourToClosedSurfaceConversionRule::CreateSmoothEndCapContour(vt
13551356
linePolyData->SetPoints(inputROIPoints->GetPoints());
13561357
linePolyData->SetLines(lines);
13571358

1359+
// Remove unused points
1360+
vtkNew<vtkCleanPolyData> cleanFilter;
1361+
cleanFilter->SetInputData(linePolyData);
1362+
cleanFilter->Update();
1363+
linePolyData->ShallowCopy(cleanFilter->GetOutput());
1364+
13581365
double bounds[6] = { 0, 0, 0, 0, 0, 0 };
13591366
linePolyData->GetBounds(bounds);
13601367

@@ -1383,7 +1390,7 @@ void vtkPlanarContourToClosedSurfaceConversionRule::CreateSmoothEndCapContour(vt
13831390
static_cast<int>(std::ceil((bounds[3] - bounds[2]) / spacing[1])),
13841391
1 };
13851392
double origin[3] = { bounds[0], bounds[2], bounds[4] };
1386-
int extent[6] = { 0, dimensions[0] - 1, 0, dimensions[1] - 1, 0, 0 };
1393+
int extent[6] = { 0, dimensions[0] - 1, 0, dimensions[1] - 1, 0, dimensions[2] - 1 };
13871394

13881395
vtkSmartPointer<vtkImageData> blankImage = vtkSmartPointer<vtkImageData>::New();
13891396
blankImage->SetSpacing(spacing);
@@ -1471,7 +1478,7 @@ void vtkPlanarContourToClosedSurfaceConversionRule::CreateSmoothEndCapContour(vt
14711478
// Calculate the decimation factor with the following formula: ( # of lines in input * number of points in original line ) / number of points in input
14721479
double decimationFactor = (1.0 * newLines->GetNumberOfLines() * inputLine->GetNumberOfPoints() + 1) / newLines->GetNumberOfPoints();
14731480

1474-
// Reduce the number of points in the line until the ration between the input and output lines meets the specified decimation factor
1481+
// Reduce the number of points in the line until the ratio between the input and output lines meets the specified decimation factor
14751482
this->DecimateLines(newLines, decimationFactor);
14761483

14771484
vtkSmartPointer<vtkPoints> inputPoints = inputROIPoints->GetPoints();

DicomRtImportExport/Testing/Python/DicomRtImportTest.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ def TestSection_RetrieveInputData(self):
5757
self.tempDir = dicomRtImportTestDir + '/Temp'
5858

5959
downloads = (
60-
('http://slicer.kitware.com/midas3/download?items=10613', 'RD.1.2.246.352.71.7.2088656855.452083.20110920153746.dcm'),
61-
('http://slicer.kitware.com/midas3/download?items=10614', 'RP.1.2.246.352.71.5.2088656855.377401.20110920153647.dcm'),
62-
('http://slicer.kitware.com/midas3/download?items=10615', 'RS.1.2.246.352.71.4.2088656855.2404649.20110920153449.dcm'),
63-
('http://slicer.kitware.com/midas3/download/item/119940', 'RI.1.2.246.352.71.3.2088656855.2381134.20110921150951.dcm')
60+
('https://github.com/SlicerRt/SlicerRtData/raw/master/eclipse-8.1.20-phantom-prostate/Original/RD.1.2.246.352.71.7.2088656855.452083.20110920153746.dcm', 'RD.1.2.246.352.71.7.2088656855.452083.20110920153746.dcm'),
61+
('https://github.com/SlicerRt/SlicerRtData/raw/master/eclipse-8.1.20-phantom-prostate/Original/RP.1.2.246.352.71.5.2088656855.377401.20110920153647.dcm', 'RP.1.2.246.352.71.5.2088656855.377401.20110920153647.dcm'),
62+
('https://github.com/SlicerRt/SlicerRtData/raw/master/eclipse-8.1.20-phantom-prostate/Original/RS.1.2.246.352.71.4.2088656855.2404649.20110920153449.dcm', 'RS.1.2.246.352.71.4.2088656855.2404649.20110920153449.dcm'),
63+
('https://github.com/SlicerRt/SlicerRtData/raw/master/eclipse-8.1.20-phantom-prostate/Original/RI.1.2.246.352.71.3.2088656855.2381134.20110921150951.dcm', 'RI.1.2.246.352.71.3.2088656855.2381134.20110921150951.dcm')
6464
)
6565

6666
for url,name in downloads:

DrrImageComputation/Testing/Python/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ if(CMAKE_CONFIGURATION_TYPES)
1616
set(MODULE_BUILD_DIR "")
1717
foreach(config ${CMAKE_CONFIGURATION_TYPES})
1818
list(APPEND MODULE_BUILD_DIR "${CMAKE_BINARY_DIR}/${Slicer_QTLOADABLEMODULES_LIB_DIR}/${config}")
19+
list(APPEND MODULE_BUILD_DIR "${CMAKE_BINARY_DIR}/${Slicer_CLIMODULES_LIB_DIR}/${config}")
1920
endforeach()
2021
else()
2122
set(MODULE_BUILD_DIR "${CMAKE_BINARY_DIR}/${Slicer_QTLOADABLEMODULES_LIB_DIR}")

ExternalBeamPlanning/Widgets/Python/OrthovoltageDoseEngine.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from DoseEngines import AbstractScriptedDoseEngine
66
from DoseEngines import OrthovoltageDoseEngineUtil
77
from DoseEngines import EGSnrcUtil
8+
from SegmentEditorEffects import SegmentEditorMaskVolumeEffect
89

910
#------------------------------------------------------------------------------
1011
#
@@ -223,14 +224,16 @@ def defineBeamParameters(self):
223224
"Z in the BEAMnrc run where the phase space source was scored (cm).\n\
224225
Only needed if DBS is enabled.", 20)
225226

227+
self.scriptedEngine.addBeamParameterLineEdit(
228+
"Orthovoltage dose", "EraseOutsideSegment", "Erase outside segment:",
229+
"If specified, causes the dose volume to be erased outside of the segment with the specified name.", "")
230+
226231
#------------------------------------------------------------------------------
227232
#TODO: Add a path parameter type using the CTK path selector that saves the selections to Application Settings
228233
def savePathsInApplicationSettings(self, beamNode):
229234
if beamNode is None:
230235
return
231236

232-
settings = qt.QSettings()
233-
234237
ctcreateExecFilePath = self.scriptedEngine.parameter(beamNode, "CtcreateExecFilePath")
235238
if ctcreateExecFilePath != self.ctcreateExecFilePathDefault:
236239
qt.QSettings().setValue('OrthovoltageDoseEngine/CtcreateExecFilePath', ctcreateExecFilePath)
@@ -284,7 +287,6 @@ def calculateDoseUsingEngine(self, beamNode, resultDoseVolumeNode):
284287
ctcreateExecFilePath = self.scriptedEngine.parameter(beamNode, "CtcreateExecFilePath")
285288
ctcreateOutputPath = self.scriptedEngine.parameter(beamNode, "CtcreateOutputPath")
286289
roiNodeName = self.scriptedEngine.parameter(beamNode, "ROIName")
287-
volumeName = self.scriptedEngine.parameter(beamNode, "VolumeName")
288290
sliceThicknessX = self.scriptedEngine.parameter(beamNode, "SliceThicknessX")
289291
sliceThicknessY = self.scriptedEngine.parameter(beamNode, "SliceThicknessY")
290292
sliceThicknessZ = self.scriptedEngine.parameter(beamNode, "SliceThicknessZ")
@@ -305,15 +307,15 @@ def calculateDoseUsingEngine(self, beamNode, resultDoseVolumeNode):
305307
##########################################
306308

307309
runCTCreateDOSXYZnrc = int(self.scriptedEngine.parameter(beamNode, "RunCTCreateDOSXYZnrc"))
308-
if runCTCreateDOSXYZnrc != self.runCTCreateAndDoseXYZnrcIndex:
310+
if runCTCreateDOSXYZnrc != self.runDOSXYZnrcOnlyIndex:
311+
logging.info("Running ctCreate")
309312
materialJSON = self.scriptedEngine.parameter(beamNode, "Materials")
310313
try:
311314
materials = json.loads(materialJSON)
312315
except:
313316
logging.error("Unable parse materials JSON string")
314317

315318
lowerBound = self.scriptedEngine.parameter(beamNode, "LowerBound")
316-
317319
OrthovoltageDoseEngineUtil.generateCtcreateInput(volumeNode, seriesUID, ctcreateOutputPath, materials, lowerBound, roiNode, thicknesses)
318320
EGSnrcUtil.callCtcreate(ctcreateExecFilePath, ctcreateOutputPath)
319321

@@ -409,7 +411,7 @@ def calculateDoseUsingEngine(self, beamNode, resultDoseVolumeNode):
409411

410412
ctcreateFilePath = ctcreateOutputPath
411413
if runCTCreateDOSXYZnrc == self.runDOSXYZnrcOnlyIndex:
412-
ctcreateFilePath = self.scriptedEngine.parameter(beamNode, "ExistingCtcreatePath")
414+
ctcreateFilePath = self.scriptedEngine.parameter(beamNode, "ExistingCtcreatePath")
413415

414416
if ctcreateFilePath == "":
415417
logging.error("ctcreate file path not specified")
@@ -504,5 +506,22 @@ def calculateDoseUsingEngine(self, beamNode, resultDoseVolumeNode):
504506
logging.info( 'Result dose volume for beam ' + beamNode.GetName() + ' successfully loaded.\n'
505507
+ ' Dose range: ({:.4f}-{:.4f})'.format(accumulate.GetMin()[0],accumulate.GetMax()[0]) )
506508

509+
# Erase dose outside of the specified segment
510+
maskSegmentId = ""
511+
maskSegmentName = self.scriptedEngine.parameter(beamNode, "EraseOutsideSegment")
512+
segmentationNode = parentPlan.GetSegmentationNode()
513+
if segmentationNode is None or segmentationNode.GetSegmentation() is None:
514+
logging.error("Could not mask dose volume. Invalid segmentation.")
515+
elif maskSegmentName != "":
516+
maskSegmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(maskSegmentName)
517+
518+
if maskSegmentId != "":
519+
logging.info(f"Erasing dose outside of {maskSegmentName}[ID: {maskSegmentId}]")
520+
operationMode = "FILL_OUTSIDE"
521+
fillValues = [0]
522+
SegmentEditorMaskVolumeEffect.maskVolumeWithSegment(segmentationNode, maskSegmentId, operationMode, fillValues, resultDoseVolumeNode, resultDoseVolumeNode)
523+
else:
524+
logging.error("Could not mask dose volume. Invalid segment id.")
525+
507526
# Successful execution, no error message
508527
return ""

0 commit comments

Comments
 (0)