diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py index 4564544..308ad4f 100644 --- a/AutoscoperM/AutoscoperM.py +++ b/AutoscoperM/AutoscoperM.py @@ -428,7 +428,6 @@ def onGenerateVRG(self): # Set up and validate inputs volumeNode = self.ui.volumeSelector.currentNode() mainOutputDir = self.ui.mainOutputSelector.currentPath - segmentationNode = self.ui.vrg_SegNodeComboBox.currentNode() width = self.ui.vrgRes_width.value height = self.ui.vrgRes_height.value nPossibleCameras = self.ui.posCamSpin.value @@ -438,7 +437,6 @@ def onGenerateVRG(self): vrgSubDir = self.ui.vrgSubDir.text if not self.logic.validateInputs( volumeNode=volumeNode, - segmentationNode=segmentationNode, mainOutputDir=mainOutputDir, width=width, height=height, @@ -457,10 +455,8 @@ def onGenerateVRG(self): logging.error("Failed to generate VRG: more optimized cameras than possible cameras") return - # Extract the subvolume for the radiographs - volumeImageData, bounds = self.logic.extractSubVolumeForVRG( - volumeNode, segmentationNode, cameraDebugMode=self.ui.camDebugCheckbox.isChecked() - ) + bounds = [0] * 6 + volumeNode.GetBounds(bounds) # Generate all possible camera positions camOffset = self.ui.camOffSetSpin.value @@ -473,7 +469,7 @@ def onGenerateVRG(self): # Generate initial VRG for each camera self.logic.generateVRGForCameras( cameras, - volumeImageData, + volumeNode, os.path.join(mainOutputDir, tmpDir), width, height, @@ -636,7 +632,6 @@ def onLoadPV(self): def onManualVRGGen(self): markupsNode = self.ui.mVRG_markupSelector.currentNode() volumeNode = self.ui.volumeSelector.currentNode() - segmentationNode = self.ui.mVRG_segmentationSelector.currentNode() mainOutputDir = self.ui.mainOutputSelector.currentPath viewAngle = self.ui.mVRG_viewAngleSpin.value clippingRange = (self.ui.mVRG_ClippingRangeSlider.minimumValue, self.ui.mVRG_ClippingRangeSlider.maximumValue) @@ -647,7 +642,6 @@ def onManualVRGGen(self): if not self.logic.validateInputs( markupsNode=markupsNode, volumeNode=volumeNode, - segmentationNode=segmentationNode, mainOutputDir=mainOutputDir, viewAngle=viewAngle, clippingRange=clippingRange, @@ -666,13 +660,9 @@ def onManualVRGGen(self): if self.logic.vrgManualCameras is None: self.onMarkupNodeChanged(markupsNode) # create the cameras - volumeImageData, _ = self.logic.extractSubVolumeForVRG( - volumeNode, segmentationNode, cameraDebugMode=self.ui.camDebugCheckbox.isChecked() - ) - self.logic.generateVRGForCameras( self.logic.vrgManualCameras, - volumeImageData, + volumeNode, os.path.join(mainOutputDir, vrgDir), width, height, @@ -682,7 +672,7 @@ def onManualVRGGen(self): self.updateProgressBar(100) for cam in self.logic.vrgManualCameras: - IO.generateCameraCalibrationFile(cam, os.path.join(mainOutputDir, cameraDir, f"cam{cam.id}.yaml")) + IO.generateCameraCalibrationFile(cam, os.path.join(mainOutputDir, cameraDir, f"cam{cam.id}.json")) def onMarkupNodeChanged(self, node): if node is None: @@ -697,12 +687,11 @@ def onMarkupNodeChanged(self, node): for cam in self.logic.vrgManualCameras: slicer.mrmlScene.RemoveNode(cam.FrustumModel) self.logic.vrgManualCameras = None - # get the volume and segmentation nodes - segmentationNode = self.ui.mVRG_segmentationSelector.currentNode() - if not self.logic.validateInputs(segmentationNode=segmentationNode): - return + # get the volume nodes + volumeNode = self.ui.volumeSelector.currentNode() + self.logic.validateInputs(volumeNode=volumeNode) bounds = [0] * 6 - segmentationNode.GetBounds(bounds) + volumeNode.GetBounds(bounds) self.logic.vrgManualCameras = RadiographGeneration.generateCamerasFromMarkups( node, bounds, @@ -1036,7 +1025,7 @@ def extractSubVolumeForVRG( def generateVRGForCameras( self, cameras: list[RadiographGeneration.Camera], - volumeImageData: vtk.vtkImageData, + volumeNode: slicer.vtkMRMLVolumeNode, outputDir: str, width: int, height: int, @@ -1047,8 +1036,8 @@ def generateVRGForCameras( :param cameras: list of cameras :type cameras: list[RadiographGeneration.Camera] - :param volumeImageData: volume image data - :type volumeImageData: vtk.vtkImageData + :param volumeNode: volume node + :type volumeNode: slicer.vtkMRMLVolumeNode :param outputDir: output directory :type outputDir: str :param width: width of the radiographs @@ -1069,9 +1058,20 @@ def generateVRGForCameras( def progressCallback(x): return x + # Apply a thresh of 0 to the volume to remove air from the volume + thresholdScalarVolume = slicer.modules.thresholdscalarvolume + parameters = { + "InputVolume": volumeNode.GetID(), + "OutputVolume": volumeNode.GetID(), + "ThresholdValue": 0, + "ThresholdType": "Below", + "Lower": 0, + } + slicer.cli.runSync(thresholdScalarVolume, None, parameters) + # write a temporary volume to disk volumeFName = "AutoscoperM_VRG_GEN_TEMP.mhd" - IO.writeTemporyFile(volumeFName, volumeImageData) + IO.writeTemporyFile(volumeFName, self.convertNodeToData(volumeNode)) # Execute CLI for each camera cliModule = slicer.modules.virtualradiographgeneration @@ -1154,3 +1154,34 @@ def progressCallback(x): progress = ((idx + 1) / len(bestCameras)) * 10 + 90 progressCallback(progress) + + def convertNodeToData(self, volumeNode: slicer.vtkMRMLVolumeNode) -> vtk.vtkImageData: + """ + Converts a volume node to a vtkImageData object + """ + imageData = vtk.vtkImageData() + imageData.DeepCopy(volumeNode.GetImageData()) + imageData.SetSpacing(volumeNode.GetSpacing()) + origin = list(volumeNode.GetOrigin()) + imageData.SetOrigin(origin) + + mat = vtk.vtkMatrix4x4() + volumeNode.GetIJKToRASMatrix(mat) + if mat.GetElement(0, 0) < 0 and mat.GetElement(1, 1) < 0: + origin[0:2] = [x * -1 for x in origin[0:2]] + imageData.SetOrigin(origin) + + # Ensure we are in the correct orientation (RAS vs LPS) + imageReslice = vtk.vtkImageReslice() + imageReslice.SetInputData(imageData) + + axes = vtk.vtkMatrix4x4() + axes.Identity() + axes.SetElement(0, 0, -1) + axes.SetElement(1, 1, -1) + + imageReslice.SetResliceAxes(axes) + imageReslice.Update() + imageData = imageReslice.GetOutput() + + return imageData diff --git a/AutoscoperM/Resources/UI/AutoscoperM.ui b/AutoscoperM/Resources/UI/AutoscoperM.ui index 12d7ac9..6e8c7fe 100644 --- a/AutoscoperM/Resources/UI/AutoscoperM.ui +++ b/AutoscoperM/Resources/UI/AutoscoperM.ui @@ -266,7 +266,7 @@ - true + false Delete Temporary VRG Files @@ -429,80 +429,85 @@ false - - + + - View Angle + Camera Positions - - + + 0.100000000000000 2000.000000000000000 - - 0.100000000000000 - - - 0.100000000000000 - - - 300.000000000000000 - - - Qt::Horizontal - - - + + - Segmentation Node + Clipping Range - + - Camera Positions + View Angle - - - - 0.100000000000000 + + + + true - - 2000.000000000000000 + + + vtkMRMLMarkupsFiducialNode + + + + + + + - - + + - Clipping Range + Generate VRGs from Markups - - + + 0.100000000000000 2000.000000000000000 - + + 0.100000000000000 + + + 0.100000000000000 + + 300.000000000000000 + + Qt::Horizontal + - + 360 @@ -512,46 +517,16 @@ - - - - true - - - - vtkMRMLSegmentationNode - - - - - - - - - - - - - - true - - - - vtkMRMLMarkupsFiducialNode - - - - + + + + 0.100000000000000 - - + + 2000.000000000000000 - - - - - - Generate VRGs from Markups + + 300.000000000000000 @@ -573,7 +548,7 @@ true - + 0 @@ -586,6 +561,20 @@ + + + + Camera Offset: + + + + + + + # of Optimized Cameras: + + + @@ -596,13 +585,6 @@ - - - - Camera Offset: - - - @@ -610,25 +592,7 @@ - - - - true - - - - vtkMRMLSegmentationNode - - - - - - - - - - - + false @@ -647,27 +611,13 @@ - + Generate VRGs - - - - Segmentation Node: - - - - - - - # of Optimized Cameras: - - - @@ -1020,22 +970,6 @@ - - AutoscoperM - mrmlSceneChanged(vtkMRMLScene*) - vrg_SegNodeComboBox - setMRMLScene(vtkMRMLScene*) - - - 508 - 619 - - - 587 - 941 - - - AutoscoperM mrmlSceneChanged(vtkMRMLScene*) @@ -1052,22 +986,6 @@ - - AutoscoperM - mrmlSceneChanged(vtkMRMLScene*) - mVRG_segmentationSelector - setMRMLScene(vtkMRMLScene*) - - - 508 - 429 - - - 507 - 409 - - - mVRG_ClippingRangeSlider minimumValueChanged(double) diff --git a/VirtualRadiographGeneration/VirtualRadiographGeneration.py b/VirtualRadiographGeneration/VirtualRadiographGeneration.py index 12ba743..dbdcf1b 100644 --- a/VirtualRadiographGeneration/VirtualRadiographGeneration.py +++ b/VirtualRadiographGeneration/VirtualRadiographGeneration.py @@ -22,6 +22,13 @@ def generateVRG( :param height: Height of the output image """ + # find the min and max scalar values + hist = vtk.vtkImageHistogramStatistics() + hist.SetInputData(volumeImageData) + hist.Update() + minVal = hist.GetMinimum() + maxVal = hist.GetMaximum() + # create the renderer renderer = vtk.vtkRenderer() renderer.SetBackground(1, 1, 1) # Set background to white @@ -42,17 +49,17 @@ def generateVRG( # Set the transfer functions for opacity, gradient and color opacityTransferFunction = vtk.vtkPiecewiseFunction() # From the Slicer CT XRay preset - opacityTransferFunction.AddPoint(0, 0.0) + opacityTransferFunction.AddPoint(minVal, 0.0) opacityTransferFunction.AddPoint(1500, 0.05) - opacityTransferFunction.AddPoint(3071, 0.05) + opacityTransferFunction.AddPoint(maxVal, 0.05) gradTransferFunction = vtk.vtkPiecewiseFunction() # From the Slicer CT XRay preset gradTransferFunction.AddPoint(0, 1) gradTransferFunction.AddPoint(255, 1) colorTransferFunction = vtk.vtkColorTransferFunction() - colorTransferFunction.AddRGBPoint(0, 1, 1, 1) # Low to be white - colorTransferFunction.AddRGBPoint(3071, 0, 0, 0) # High to be black + colorTransferFunction.AddRGBPoint(maxVal, 1, 1, 1) + colorTransferFunction.AddRGBPoint(minVal, 0, 0, 0) volumeProperty = vtk.vtkVolumeProperty() volumeProperty.SetInterpolationTypeToLinear()