I have this file structure:
Main Folder: W001-W100
.
Subfolder: W001
Subfolder: W002
Subfolder: ...
Subfolder: W100
I am using Slicer software and Python to automate the process of getting surface meshes automatically.
This is the Python script I wrote that needs to be run in each subfolder.
As you can see, there are two lines with the location of the subfolder:
.ply
file.As I have around 1000 subfolders, I don't want to run the script changing those lines. In other words, I wan to automate the process.
I am much more familiar with R than with Python. So:
# Load DICOM files
dicomDataDir = "C:/Users/mario.modesto/Desktop/DICOM/W001" # input folder with DICOM files
import os
baboon_skull = os.path.basename(dicomDataDir)
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs
from DICOMLib import DICOMUtils
with DICOMUtils.TemporaryDICOMDatabase() as db:
DICOMUtils.importDicom(dicomDataDir, db)
patientUIDs = db.patients()
for patientUID in patientUIDs:
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))
# Display volume rendering
logic = slicer.modules.volumerendering.logic()
volumeNode = slicer.mrmlScene.GetNodeByID('vtkMRMLScalarVolumeNode1')
displayNode = logic.CreateVolumeRenderingDisplayNode()
displayNode.UnRegister(logic)
slicer.mrmlScene.AddNode(displayNode)
volumeNode.AddAndObserveDisplayNodeID(displayNode.GetID())
logic.UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode)
# find the files NodeID
volumeNode = getNode('2: Facial Bones 0.75 H70h')
#create a blank Markup ROI
roiNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsROINode")
#set the new markup ROI to the dimensions of the volume
cropVolumeParameters = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLCropVolumeParametersNode")
cropVolumeParameters.SetInputVolumeNodeID(volumeNode.GetID())
cropVolumeParameters.SetROINodeID(roiNode.GetID())
slicer.modules.cropvolume.logic().SnapROIToVoxelGrid(cropVolumeParameters) # optional (rotates the ROI to match the volume axis directions)
slicer.modules.cropvolume.logic().FitROIToInputVolume(cropVolumeParameters)
slicer.mrmlScene.RemoveNode(cropVolumeParameters)
#set the cropping parameters
cropVolumeLogic = slicer.modules.cropvolume.logic()
cropVolumeParameterNode = slicer.vtkMRMLCropVolumeParametersNode()
cropVolumeParameterNode.SetIsotropicResampling(True)
#set the output resolution to 2 millimeters. units in slicer is always in mm.
cropVolumeParameterNode.SetSpacingScalingConst(2)
cropVolumeParameterNode.SetROINodeID(roiNode.GetID())
cropVolumeParameterNode.SetInputVolumeNodeID(volumeNode.GetID())
#do the cropping
cropVolumeLogic.Apply(cropVolumeParameterNode)
#obtain the nodeID of the cropped volume
croppedVolume = slicer.mrmlScene.GetNodeByID(cropVolumeParameterNode.GetOutputVolumeNodeID())
# Segmentation
segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
segmentationNode.CreateDefaultDisplayNodes() # only needed for display
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(croppedVolume)
addedSegmentID = segmentationNode.GetSegmentation().AddEmptySegment("skull")
# Create segment editor to get access to effects
segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
segmentEditorNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentEditorNode")
segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
segmentEditorWidget.setSegmentationNode(segmentationNode)
segmentEditorWidget.setMasterVolumeNode(croppedVolume)
# Thresholding
segmentEditorWidget.setActiveEffectByName("Threshold")
effect = segmentEditorWidget.activeEffect()
effect.setParameter("MinimumThreshold","115")
effect.setParameter("MaximumThreshold","3071")
effect.self().onApply()
# Clean up
segmentEditorWidget = None
slicer.mrmlScene.RemoveNode(segmentEditorNode)
# Make segmentation results visible in 3D
segmentationNode.CreateClosedSurfaceRepresentation()
# Creatint surface mesh and saving
surfaceMesh = segmentationNode.GetClosedSurfaceInternalRepresentation(addedSegmentID)
writer = vtk.vtkPLYWriter()
writer.SetInputData(surfaceMesh)
writer.SetFileName("C:/Users/mario.modesto/Desktop/DICOM/"+baboon_skull+"_surfaceMesh.ply")
writer.Update()
I would really appreciate your help.
First, for readability reasons, I really would suggest to move the import
statements to the top. (Except you have any on demand imports, which is not the case in your posted script)
Second, screening of the directory and applying the function to each folder should do the job:
import os
from DICOMLib import DICOMUtils
yourpath = r"<enter your path>/W001-W100"
#walk through DICOM directory
for dir in os.scandir(yourpath):
# Load DICOM files
dicomDataDir = dir.path # path to input folder with DICOM files
baboon_skull = dir.name
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs
with DICOMUtils.TemporaryDICOMDatabase() as db:
DICOMUtils.importDicom(dicomDataDir, db)
patientUIDs = db.patients()
for patientUID in patientUIDs:
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))
< rest of your code >
writer.SetFileName(fr"{dicomDataDir}_surfaceMesh.ply")
writer.Update()