gaudot / SlicerDentalSegmentator

3D Slicer extension for fully-automatic segmentation of CT and CBCT dental volumes.
Apache License 2.0
36 stars 3 forks source link

Batch Processing NIfTI Data with DentalSegmentator: Import, Segment, and Export #7

Open Bill-Yang-1 opened 1 month ago

Bill-Yang-1 commented 1 month ago

Dear Developers,

Thank you for providing such a perfect extension package! I still have the following questions to consult with you:

Currently, I have a series of NIfTI format data, approximately 300 files, that I would like to process in batch as follows:

  1. Import the NIfTI data.
  2. Run DentalSegmentator to obtain the following five segmentations: "Maxilla & Upper Skull," "Mandible," "Upper Teeth," "Lower Teeth," and "Mandibular canal."
  3. Merge the above segmentations and export them in NIfTI and .obj format.

I am curious about how to specifically implement the above steps using your code. Additionally, can the involved steps, such as "import slicer," only be run in the Python environment built into the Slicer software, or can they be executed in other IDEs like VS Code? If so, which libraries need to be installed?

Looking forward to your reply!

Best regards, Bill-Yang, 3323015549@qq.com

Thibault-Pelletier commented 1 month ago

Hi @Bill-Yang-1,

To do batch processing, you can instantiate the widget in your code and use it as a component as used in the integration test https://github.com/gaudot/SlicerDentalSegmentator/blob/main/DentalSegmentator/Testing/IntegrationTestCase.py#L31

Your code would look something like this :

# Create the widget
widget = SegmentationWidget()

# Set export formats
selectedFormats = ExportFormat.OBJ | ExportFormat.NIFTI 

# Iterate over the files 
for f in input_files:
  # Set output folder for input file
  folderPath = ...

  # Load file
  node = slicer.util.loadVolume(f)

  # Set widget input and launch process
  widget.inputSelector.setCurrentNode(node)
  widget.applyButton.clicked()
  widget.logic.waitForSegmentationFinished()
  slicer.app.processEvents()

  segmentationNode = list(slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode"))[0]
  # Export segmentation
  exportSegmentation(segmentationNode, folderPath, selectedFormats)

  # Cleanup scene for next batch
  slicer.mrmlScene.RemoveNode(segmentationNode)
  slicer.mrmlScene.RemoveNode(node )

You will probably have to add some logic to handle possible failure cases. You can also hook the progress information display to a print function (you can have a look at https://github.com/gaudot/SlicerDentalSegmentator/blob/main/DentalSegmentator/DentalSegmentatorLib/SegmentationWidget.py#L570 for the associated logic)

Best, Thibault

Bill-Yang-1 commented 1 month ago

Thank you very much for your quick response. Here is the code I wrote in the Windows environment based on your provided example:

import slicer
from DentalSegmentatorLib import PythonDependencyChecker, SegmentationWidget, ExportFormat
import unittest
from pathlib import Path
import qt
import os
import sys

def load_nii_file(filepath):
    return slicer.util.loadVolume(filepath)

def single_segmentation(filepath):
    print(f'cur_file = {filepath}')

    outputpath = filepath[:filepath.rfind('\\')]
    outputfolder = os.path.join(outputpath, 'segmentation')
    os.makedirs(outputfolder, exist_ok=True)

    widget = SegmentationWidget()
    selectedFormats = ExportFormat.OBJ | ExportFormat.NIFTI 

    node = load_nii_file(filepath)

    widget.inputSelector.setCurrentNode(node)
    widget.applyButton.clicked()
    widget.logic.waitForSegmentationFinished()
    slicer.app.processEvents()

    segmentationNode = list(slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode"))[0]

    segmentation = segmentationNode.GetSegmentation()
    labels = ["Maxilla & Upper Skull", "Mandible", "Upper Teeth", "Lower Teeth", "Mandibular canal"]
    segmentIds = [f"Segment_{i + 1}" for i in range(len(labels))]

    for segmentId, label in zip(segmentIds, labels):
        segment = segmentation.GetSegment(segmentId)
        if segment is None:
            continue
        segment.SetName(label)
        singleSegmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
        singleSegmentationNode.SetName(label)
        singleSegmentationNode.GetSegmentation().AddSegment(segment)
        widget.exportSegmentation(singleSegmentationNode, outputfolder, selectedFormats)
        slicer.mrmlScene.RemoveNode(singleSegmentationNode)

    widget.exportSegmentation(segmentationNode, outputfolder, selectedFormats)

    slicer.mrmlScene.RemoveNode(segmentationNode)
    slicer.mrmlScene.RemoveNode(node)
    # print('finished!')

def batch_segmentation(filepath):
    for dirname in os.listdir(filepath):
        pre_filepath = os.path.join(filepath, dirname, 'pre', 'pre_ct.nii.gz')
        single_segmentation(pre_filepath)
        post_filepath = os.path.join(filepath, dirname, 'post', 'post_ct.nii.gz')
        single_segmentation(post_filepath)

filepath = r'F:\3D2CT\datasets\LUCY_CT'
batch_segmentation(filepath)

However, when I run the above code in Slicer's built-in Python console, I encounter the following error:

Failed to load the segmentation.
Check the inference folder content C:\Users\Yangjw\AppData\Local\Temp\Slicer-gcrSYo\output
Failed to load the segmentation.
Check the inference folder content C:\Users\Yangjw\AppData\Local\Temp\Slicer-RmBuWm\output

QQ图片20240528032434 How can I resolve this issue? Looking forward to your response. Thank you!

Best regards, Bill-Yang, 3323015549@qq.com

Thibault-Pelletier commented 1 month ago

@Bill-Yang-1 this error occurs when the segmentation failed on the NNUNet side.

It happens when the inference has finished and indicates that no NIFTI was generated in the output folder set-up by the SlicerNNUNet module.

You should check if you have any information in the progress information and also check if launching the segmentation from the UI on these cases works for you.

To connect the information signals you can connect the following :

widget.logic.progressInfo.connect(<your function>)
widget.logic.errorOccured.connect(<your function>)
widget.logic.inferenceFinished.connect(<your function>)

Note also that you don't need to manually rename the segments. This is already done by the module in the _loadSegmentationResults method which triggers the error.

dentistfrankchen commented 4 weeks ago

@Bill-Yang-1 Are you running your code with gpu when you encounter the error? I met a smiliar issue and solved it by changing gpu to cpu. I was wondering if you could check this and see if this helps. Thank you.