Converting .stl files to binary label maps in .nii format using Python

Dear all,

I hope you’re doing well. I’ve spent the last week or two struggling to convert a large number of .stl files (~1,000) drawn in 3D Slicer 4.10.2 into .nii binary label maps using Python. The .stl data were derived from segmentations drawn on Dixon MRI .nii data that I’m using as the reference volumes for the conversions. I spent some time working with the example code from the Wiki (Documentation), but with little success. Looking through the forum here I found some good tips (e.g. Load a .stl file and save it to a binary label map), and I came up with the following script:

stl_list = sys.argv[2:]  # Get list of .stl files
dixon_path = os.path.join(sys.argv[1], 'mDIXONvol.nii.gz')
dixonVolumeNode = slicer.util.loadVolume(dixon_path, returnNode=True)[1]

for stl_file_name in stl_list:
	t = time()

	# Extract abbreviated ROI name from .stl filename	
	stl_name = os.path.splitext(stl_file_name.split("_")[-1])[0]
	
	segmentation = slicer.util.loadSegmentation(stl_file_name, returnNode=True)[1]
	ids = vtk.vtkStringArray()
	segmentation.GetDisplayNode().GetVisibleSegmentIDs(ids)	

	outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
	slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentation, ids, outputLabelmapVolumeNode, dixonVolumeNode)
	slicer.util.saveNode(outputLabelmapVolumeNode, os.path.join(cwd, stl_name + ".nii.gz"))
	print("Converted " + stl_name + ".stl to " + stl_name + ".nii in " + str(round(time() - t, 3)) + "s")
exit()

Unfortunately, this leads to blank .nii files (all zeros), which becomes apparent when I load the .nii labelmaps in the 3D Slicer GUI. I can’t figure out why this is happening. I do, however, get the following warning during processing:

CalculateOutputGeometry: No image geometry specified, default geometry is calculated (0.333381665360425;0;0;30.7908134460449;0;0.333381665360425;0;-72.2878036499023;0;0;0.333381665360425;-9.96676349639893;0;0;0;1;0;160;0;179;0;543;)

I’m not sure why this warning is appearing, given that I’m using the original reference volumes for the conversion. Could there be a coordinate system issue here? I’d really appreciate any insights you might be able to share here.

With gratitude,
Donnie

The scripts are probably intended for latest Slicer Preview Release. Let us know if you have trouble making them work with that Slicer version.

Thanks very much for the tip. Using Slicer 4.11 I’m still getting empty .nii volumes with the script above, but I’ll have another go at the code from the script repository and I’ll report back.

Just to give some helpful context, using the ‘Model to LabelMap’ tool in the GUI produces the desired results, so the underlying data appear to be OK.

This works well for me:

stl_file_name = ...
output_file_name = ...
reference_volume_path = ...

referenceVolumeNode = slicer.util.loadVolume(reference_volume_path)
segmentationNode = slicer.util.loadSegmentation(stl_file_name)
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, outputLabelmapVolumeNode, referenceVolumeNode)
slicer.util.saveNode(outputLabelmapVolumeNode, output_file_name)

Try this and if it works for you, too, then start modifying it line-by-line to see where it goes wrong.

3 Likes

Thanks very much for the code. I gave it a try, and I’m still getting the following message: “CalculateOutputGeometry: No image geometry specified, default geometry is calculated”. The output volume is still just an array of zeros as well. I’m not sure why this happens, and I’ve confirmed that the reference volumes and models are all loaded correctly…

I was, however, able to get the example code from the script repository to work in Slicer 4.11, after some modifications. Here is my working code:

referenceVolumeNode = slicer.util.loadVolume(ref_volume)
inputModel = slicer.util.loadModel(stl_file)

# Convert model to labelmap
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
seg.CreateBinaryLabelmapRepresentation()
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)

# Set image directions to correct flip L-R issue
imageDirections = [[-1,0,0], [0,1,0], [0,0,1]] 
outputLabelmapVolumeNode.SetIJKToRASDirections(imageDirections)
	
slicer.util.saveNode(outputLabelmapVolumeNode, "seg.nii.gz"))

Aside from the unexpected left-right flip, which I’ve corrected here, the code works like a charm. Thanks very much for your help!

Thanks for sharing the script that worked for you. Probably the very latest Slicer Preview Release (that you download today or later) would work with the shorter script I provided (@Sam_Horvath pushed a fix for properly handling STL files in either RAS or LPS coordinate system).

Sorry for the slow reply. I downloaded the latest Slicer Preview Release (2020-09-01) to test the shorter script you provided and the code now works! The “CalculateOutputGeometry: No image geometry specified, default geometry is calculated” message still appears, but the resulting label maps are perfect, and do not exhibit the left-right flip I mentioned above. Thanks again for your help with this issue.

1 Like

Hi,

Thank you for this thread. I am able to convert my Model ‘.stl’ files to Segmentations ‘.seg.nrrd’ files.
I am not facing the direction related issue. But I wanted to know how to set the Segmentation labelmap geometry using python code, rather than using Segment Editor in GUI.

My Code -

referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass(‘vtkMRMLScalarVolumeNode’) #Gets the already loaded DICOM Volume
inputModel = slicer.util.loadModel(model_filepath) # .stl filepath

seg = slicer.mrmlScene.AddNewNodeByClass(‘vtkMRMLSegmentationNode’)
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
seg.CreateBinaryLabelmapRepresentation()
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass(“vtkMRMLLabelMapVolumeNode”)
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, labelmapVolumeNode, referenceVolumeNode)

slicer.util.saveNode(referenceVolumeNode, output_volume_filepath))
slicer.util.saveNode(seg, output_segmentation_filepath)

My segmentation geometry is currently:

image

I need to set my segmentation geometry to this, using python script code.

image

Kindly help.

Thank you.

See a complete example for specifying segmentation geometry in the script repository.

1 Like

A post was merged into an existing topic: stl file stores the annotation results of CT data, and I want to convert it to nii format for deep learning.

Hello sir, I don’t know if you have time, but I would like to ask you a question.
After converting STL data to Nii.gz data, my own STL file was complete, but I found that many neural tubes were broken after conversion. I don’t know how to reduce this kind of breakage. Do you have a good solution

Hello friends,
I have a folder containing STL masks of lesions from my dataset, and I want to extract Radiomics features. Should I first convert the STLs to binary masks? If it’s not necessary to convert them for feature extraction, please let me know how to extract features directly from STL files and a reference image. I would greatly appreciate a Python code for batch processing rather than single-image mode. Thank you!